From d06d0cf1ed6d1c796d15a09d4808c4ed3a42025d Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 10 Feb 2021 14:11:52 +0200 Subject: [PATCH] Update eslint-config-chartjs to v0.3.0 --- package-lock.json | 6 +- package.json | 2 +- samples/.eslintrc.yml | 8 +- src/controllers/controller.bar.js | 918 ++--- src/controllers/controller.bubble.js | 304 +- src/controllers/controller.doughnut.js | 720 ++-- src/controllers/controller.line.js | 398 +- src/controllers/controller.pie.js | 2 +- src/controllers/controller.polarArea.js | 388 +- src/controllers/controller.radar.js | 244 +- src/controllers/controller.scatter.js | 54 +- src/core/core.adapters.js | 68 +- src/core/core.animation.js | 212 +- src/core/core.animations.js | 416 +- src/core/core.animator.js | 350 +- src/core/core.config.js | 274 +- src/core/core.controller.js | 1844 ++++----- src/core/core.datasetController.js | 1740 ++++---- src/core/core.defaults.js | 168 +- src/core/core.element.js | 58 +- src/core/core.interaction.js | 384 +- src/core/core.intl.js | 18 +- src/core/core.layouts.js | 618 +-- src/core/core.plugins.js | 216 +- src/core/core.registry.js | 236 +- src/core/core.scale.js | 3038 +++++++------- src/core/core.ticks.js | 76 +- src/core/core.typedRegistry.js | 144 +- src/elements/element.arc.js | 336 +- src/elements/element.bar.js | 354 +- src/elements/element.line.js | 538 +-- src/elements/element.point.js | 138 +- src/helpers/helpers.canvas.js | 532 +-- src/helpers/helpers.collection.js | 198 +- src/helpers/helpers.color.js | 8 +- src/helpers/helpers.core.js | 264 +- src/helpers/helpers.curve.js | 330 +- src/helpers/helpers.dom.js | 276 +- src/helpers/helpers.easing.js | 448 +-- src/helpers/helpers.extras.js | 44 +- src/helpers/helpers.interpolation.js | 36 +- src/helpers/helpers.math.js | 162 +- src/helpers/helpers.options.js | 198 +- src/helpers/helpers.rtl.js | 102 +- src/helpers/helpers.segment.js | 330 +- src/index.js | 2 +- src/platform/platform.base.js | 54 +- src/platform/platform.basic.js | 12 +- src/platform/platform.dom.js | 568 +-- src/plugins/plugin.decimation.js | 256 +- src/plugins/plugin.filler.js | 902 ++--- src/plugins/plugin.legend.js | 1142 +++--- src/plugins/plugin.title.js | 314 +- src/plugins/plugin.tooltip.js | 2132 +++++----- src/scales/scale.category.js | 226 +- src/scales/scale.linear.js | 60 +- src/scales/scale.linearbase.js | 444 +-- src/scales/scale.logarithmic.js | 322 +- src/scales/scale.radialLinear.js | 1000 ++--- src/scales/scale.time.js | 900 ++--- src/scales/scale.timeseries.js | 208 +- test/BasicChartWebWorker.js | 26 +- .../backgroundColor/indexable.js | 96 +- .../backgroundColor/loopable.js | 82 +- .../backgroundColor/scriptable.js | 98 +- .../controller.bar/backgroundColor/value.js | 68 +- .../fixtures/controller.bar/bar-base-value.js | 54 +- .../bar-default-begin-at-zero.js | 50 +- .../bar-skip-null-object-data.js | 60 +- test/fixtures/controller.bar/bar-skip-null.js | 62 +- test/fixtures/controller.bar/border-radius.js | 82 +- .../controller.bar/borderColor/indexable.js | 100 +- .../controller.bar/borderColor/scriptable.js | 102 +- .../controller.bar/borderColor/value.js | 72 +- .../controller.bar/borderSkipped/indexable.js | 102 +- .../borderSkipped/scriptable.js | 104 +- .../controller.bar/borderSkipped/value.js | 104 +- .../borderWidth/indexable-object.js | 104 +- .../controller.bar/borderWidth/indexable.js | 100 +- .../controller.bar/borderWidth/negative.js | 90 +- .../controller.bar/borderWidth/object.js | 76 +- .../borderWidth/scriptable-object.js | 92 +- .../controller.bar/borderWidth/scriptable.js | 88 +- .../controller.bar/borderWidth/value.js | 72 +- .../controller.bar/chart-area-clip.js | 76 +- test/fixtures/controller.bar/data/object.js | 56 +- test/fixtures/controller.bar/data/parsing.js | 80 +- .../floatBar/data-as-objects-horizontal.js | 58 +- .../floatBar/data-as-objects.js | 56 +- .../controller.bar/horizontal-borders.js | 78 +- .../minBarLength/horizontal-neg.js | 62 +- .../minBarLength/horizontal-pos.js | 62 +- .../controller.bar/minBarLength/horizontal.js | 62 +- .../minBarLength/vertical-neg.js | 60 +- .../minBarLength/vertical-pos.js | 60 +- .../controller.bar/minBarLength/vertical.js | 60 +- .../stacking/logarithmic-strings.js | 64 +- .../controller.bar/stacking/logarithmic.js | 64 +- test/fixtures/controller.bubble/clip.js | 64 +- .../fixtures/controller.bubble/radius-data.js | 86 +- .../controller.bubble/radius-scriptable.js | 82 +- .../backgroundColor/indexable.js | 88 +- .../backgroundColor/scriptable.js | 84 +- .../backgroundColor/value.js | 60 +- .../borderAlign/indexable.js | 96 +- .../borderAlign/scriptable.js | 80 +- .../controller.doughnut/borderAlign/value.js | 68 +- .../borderColor/indexable.js | 92 +- .../borderColor/scriptable.js | 88 +- .../controller.doughnut/borderColor/value.js | 64 +- .../borderWidth/indexable.js | 92 +- .../borderWidth/scriptable.js | 74 +- .../controller.doughnut/borderWidth/value.js | 64 +- .../doughnut-circumference-per-dataset.js | 58 +- .../controller.doughnut/doughnut-hidden.js | 78 +- .../doughnut-rotation-per-dataset.js | 94 +- .../backgroundColor/scriptable.js | 122 +- .../controller.line/backgroundColor/value.js | 94 +- .../borderCapStyle/scriptable.js | 120 +- .../controller.line/borderCapStyle/value.js | 96 +- .../controller.line/borderColor/scriptable.js | 114 +- .../controller.line/borderColor/value.js | 84 +- .../controller.line/borderDash/scriptable.js | 96 +- .../controller.line/borderDash/value.js | 86 +- .../borderDashOffset/scriptable.js | 98 +- .../controller.line/borderDashOffset/value.js | 90 +- .../borderJoinStyle/scriptable.js | 114 +- .../controller.line/borderJoinStyle/value.js | 96 +- .../controller.line/borderWidth/scriptable.js | 110 +- .../controller.line/borderWidth/value.js | 86 +- .../controller.line/borderWidth/zero.js | 90 +- .../cubicInterpolationMode/scriptable.js | 92 +- .../cubicInterpolationMode/value.js | 84 +- .../controller.line/fill/order-default.js | 94 +- test/fixtures/controller.line/fill/order.js | 94 +- .../controller.line/fill/scriptable.js | 98 +- test/fixtures/controller.line/fill/value.js | 90 +- .../pointBackgroundColor/indexable.js | 104 +- .../pointBackgroundColor/scriptable.js | 106 +- .../pointBackgroundColor/value.js | 76 +- .../pointBorderColor/indexable.js | 104 +- .../pointBorderColor/scriptable.js | 106 +- .../controller.line/pointBorderColor/value.js | 76 +- .../pointBorderWidth/indexable.js | 88 +- .../pointBorderWidth/scriptable.js | 106 +- .../controller.line/pointBorderWidth/value.js | 80 +- .../controller.line/pointStyle/indexable.js | 112 +- .../controller.line/pointStyle/scriptable.js | 114 +- .../controller.line/pointStyle/value.js | 80 +- .../controller.line/radius/indexable.js | 86 +- .../controller.line/radius/scriptable.js | 104 +- test/fixtures/controller.line/radius/value.js | 78 +- .../controller.line/rotation/indexable.js | 90 +- .../controller.line/rotation/scriptable.js | 108 +- .../controller.line/rotation/value.js | 82 +- .../controller.line/showLine/dataset.js | 76 +- .../controller.line/showLine/false.js | 62 +- .../controller.line/stacking/order-default.js | 94 +- .../stacking/order-specified.js | 98 +- .../stacking/stacked-scatter.js | 144 +- .../backgroundColor/indexable-dataset.js | 66 +- .../indexable-element-options.js | 74 +- .../backgroundColor/scriptable-dataset.js | 64 +- .../scriptable-element-options.js | 72 +- .../backgroundColor/value-dataset.js | 52 +- .../backgroundColor/value-element-options.js | 60 +- .../borderAlign/indexable-dataset.js | 82 +- .../borderAlign/indexable-element-options.js | 80 +- .../borderAlign/scriptable-dataset.js | 74 +- .../borderAlign/scriptable-element-options.js | 72 +- .../borderAlign/value-dataset.js | 68 +- .../borderAlign/value-element-options.js | 66 +- .../borderColor/indexable-dataset.js | 78 +- .../borderColor/indexable-element-options.js | 78 +- .../borderColor/scriptable-dataset.js | 76 +- .../borderColor/scriptable-element-options.js | 76 +- .../borderColor/value-dataset.js | 64 +- .../borderColor/value-element-options.js | 64 +- .../borderWidth/indexable-dataset.js | 78 +- .../borderWidth/indexable-element-options.js | 78 +- .../borderWidth/scriptable-dataset.js | 70 +- .../borderWidth/scriptable-element-options.js | 68 +- .../borderWidth/value-dataset.js | 64 +- .../borderWidth/value-element-options.js | 64 +- .../backgroundColor/scriptable.js | 114 +- .../controller.radar/backgroundColor/value.js | 92 +- .../borderCapStyle/scriptable.js | 118 +- .../controller.radar/borderCapStyle/value.js | 100 +- .../borderColor/scriptable.js | 106 +- .../controller.radar/borderColor/value.js | 82 +- .../controller.radar/borderDash/scriptable.js | 90 +- .../controller.radar/borderDash/value.js | 84 +- .../borderDashOffset/scriptable.js | 102 +- .../borderDashOffset/value.js | 94 +- .../borderJoinStyle/scriptable.js | 118 +- .../controller.radar/borderJoinStyle/value.js | 100 +- .../borderWidth/scriptable.js | 102 +- .../controller.radar/borderWidth/value.js | 84 +- .../controller.radar/borderWidth/zero.js | 88 +- .../controller.radar/fill/scriptable.js | 96 +- test/fixtures/controller.radar/fill/value.js | 88 +- .../pointBackgroundColor/indexable.js | 108 +- .../pointBackgroundColor/scriptable.js | 104 +- .../pointBackgroundColor/value.js | 80 +- .../pointBorderColor/indexable.js | 110 +- .../pointBorderColor/scriptable.js | 106 +- .../pointBorderColor/value.js | 82 +- .../pointBorderWidth/indexable.js | 92 +- .../pointBorderWidth/scriptable.js | 104 +- .../pointBorderWidth/value.js | 84 +- .../controller.radar/pointStyle/indexable.js | 116 +- .../controller.radar/pointStyle/scriptable.js | 112 +- .../controller.radar/pointStyle/value.js | 84 +- .../controller.radar/radius/indexable.js | 90 +- .../controller.radar/radius/scriptable.js | 102 +- .../fixtures/controller.radar/radius/value.js | 82 +- .../controller.radar/rotation/indexable.js | 94 +- .../controller.radar/rotation/scriptable.js | 106 +- .../controller.radar/rotation/value.js | 86 +- .../controller.radar/showLine/value.js | 104 +- .../controller.scatter/showLine/true.js | 58 +- .../controller.scatter/showLine/undefined.js | 56 +- test/fixtures/core.layouts/long-labels.js | 68 +- test/fixtures/core.layouts/scriptable.js | 94 +- .../cross-align-bottom-center.js | 50 +- .../crossAlignment/cross-align-bottom-far.js | 50 +- .../crossAlignment/cross-align-bottom-near.js | 50 +- .../crossAlignment/cross-align-left-center.js | 54 +- .../cross-align-left-far-clipped.js | 60 +- .../crossAlignment/cross-align-left-far.js | 54 +- .../crossAlignment/cross-align-left-near.js | 54 +- .../cross-align-right-center.js | 54 +- .../cross-align-right-far-clipped.js | 62 +- .../crossAlignment/cross-align-right-far.js | 54 +- .../crossAlignment/cross-align-right-near.js | 54 +- .../crossAlignment/cross-align-top-center.js | 52 +- .../crossAlignment/cross-align-top-far.js | 52 +- .../crossAlignment/cross-align-top-near.js | 52 +- .../fixtures/core.scale/label-align-center.js | 60 +- test/fixtures/core.scale/label-align-end.js | 60 +- test/fixtures/core.scale/label-align-start.js | 60 +- test/fixtures/element.line/default.js | 44 +- test/fixtures/element.line/skip/all.js | 50 +- test/fixtures/element.line/skip/first-span.js | 52 +- test/fixtures/element.line/skip/first.js | 50 +- test/fixtures/element.line/skip/last-span.js | 52 +- test/fixtures/element.line/skip/last.js | 50 +- .../fixtures/element.line/skip/middle-span.js | 52 +- test/fixtures/element.line/skip/middle.js | 50 +- test/fixtures/element.line/stepped/after.js | 52 +- test/fixtures/element.line/stepped/before.js | 52 +- test/fixtures/element.line/stepped/default.js | 52 +- test/fixtures/element.line/stepped/middle.js | 52 +- test/fixtures/element.line/tension/default.js | 48 +- test/fixtures/element.line/tension/one.js | 50 +- test/fixtures/element.line/tension/zero.js | 50 +- .../element.point/point-style-image.js | 64 +- test/fixtures/element.point/rotation.js | 88 +- test/fixtures/mixed/bar+line.js | 74 +- .../fill-line-dataset-interpolated.js | 122 +- .../plugin.filler/fill-radar-dataset-order.js | 62 +- .../title/bottom-center-center.js | 68 +- .../plugin.legend/title/bottom-end-end.js | 68 +- .../plugin.legend/title/bottom-start-start.js | 68 +- .../plugin.legend/title/left-center-center.js | 68 +- .../plugin.legend/title/left-end-end.js | 68 +- .../plugin.legend/title/left-start-start.js | 68 +- .../title/right-center-center.js | 68 +- .../plugin.legend/title/right-end-end.js | 68 +- .../plugin.legend/title/right-start-start.js | 68 +- .../plugin.legend/title/top-center-center.js | 68 +- .../plugin.legend/title/top-end-end.js | 68 +- .../plugin.legend/title/top-start-start.js | 68 +- test/fixtures/plugin.tooltip/opacity.js | 176 +- test/fixtures/plugin.tooltip/point-style.js | 138 +- .../scale.category/ticks-from-data.js | 54 +- test/fixtures/scale.linear/tiny-numbers.js | 28 +- .../anglelines-indexable.js | 52 +- .../anglelines-scriptable.js | 60 +- .../gridlines-scriptable.js | 64 +- test/fixtures/scale.time/autoskip-major.js | 68 +- .../scale.time/bar-large-gap-between-data.js | 104 +- test/fixtures/scale.time/custom-parser.js | 76 +- test/fixtures/scale.time/data-ty.js | 106 +- test/fixtures/scale.time/data-xy.js | 100 +- test/fixtures/scale.time/labels-date.js | 46 +- test/fixtures/scale.time/labels-strings.js | 42 +- test/fixtures/scale.time/labels.js | 82 +- test/fixtures/scale.time/negative-times.js | 78 +- .../fixtures/scale.time/source-auto-linear.js | 58 +- .../fixtures/scale.time/source-data-linear.js | 58 +- .../source-labels-linear-offset-min-max.js | 62 +- .../scale.time/source-labels-linear.js | 58 +- test/fixtures/scale.time/ticks-capacity.js | 52 +- test/fixtures/scale.time/ticks-minunit.js | 50 +- .../ticks-reverse-linear-min-max.js | 62 +- .../scale.time/ticks-reverse-linear.js | 58 +- .../scale.time/ticks-reverse-offset.js | 60 +- test/fixtures/scale.time/ticks-reverse.js | 58 +- test/fixtures/scale.time/ticks-round.js | 52 +- test/fixtures/scale.time/ticks-stepsize.js | 52 +- test/fixtures/scale.time/ticks-unit.js | 48 +- .../scale.timeseries/financial-daily.js | 108 +- test/fixtures/scale.timeseries/source-auto.js | 56 +- .../source-data-offset-min-max.js | 60 +- test/fixtures/scale.timeseries/source-data.js | 56 +- .../source-labels-offset-min-max.js | 60 +- .../scale.timeseries/source-labels.js | 56 +- .../scale.timeseries/ticks-reverse-max.js | 58 +- .../scale.timeseries/ticks-reverse-min-max.js | 60 +- .../scale.timeseries/ticks-reverse-min.js | 58 +- .../scale.timeseries/ticks-reverse.js | 56 +- test/index.js | 6 +- test/specs/controller.bar.tests.js | 3214 +++++++-------- test/specs/controller.bubble.tests.js | 758 ++-- test/specs/controller.doughnut.tests.js | 834 ++-- test/specs/controller.line.tests.js | 1868 ++++----- test/specs/controller.polarArea.tests.js | 688 ++-- test/specs/controller.radar.tests.js | 802 ++-- test/specs/controller.scatter.tests.js | 126 +- test/specs/core.animation.tests.js | 138 +- test/specs/core.animations.tests.js | 372 +- test/specs/core.animator.tests.js | 90 +- test/specs/core.controller.tests.js | 3498 ++++++++--------- test/specs/core.datasetController.tests.js | 1718 ++++---- test/specs/core.defaults.tests.js | 58 +- test/specs/core.element.tests.js | 24 +- test/specs/core.helpers.tests.js | 36 +- test/specs/core.interaction.tests.js | 1566 ++++---- test/specs/core.layouts.tests.js | 1134 +++--- test/specs/core.plugin.tests.js | 688 ++-- test/specs/core.registry.tests.js | 516 +-- test/specs/core.scale.tests.js | 1184 +++--- test/specs/core.ticks.tests.js | 176 +- test/specs/element.arc.tests.js | 164 +- test/specs/element.bar.tests.js | 108 +- test/specs/element.line.tests.js | 58 +- test/specs/element.point.tests.js | 114 +- test/specs/global.defaults.tests.js | 506 +-- test/specs/global.namespace.tests.js | 66 +- test/specs/helpers.canvas.tests.js | 614 +-- test/specs/helpers.collection.tests.js | 58 +- test/specs/helpers.color.tests.js | 70 +- test/specs/helpers.core.tests.js | 912 ++--- test/specs/helpers.curve.tests.js | 354 +- test/specs/helpers.dom.tests.js | 700 ++-- test/specs/helpers.easing.tests.js | 104 +- test/specs/helpers.interpolation.tests.js | 52 +- test/specs/helpers.math.tests.js | 270 +- test/specs/helpers.options.tests.js | 466 +-- test/specs/helpers.segment.tests.js | 80 +- test/specs/mixed.tests.js | 2 +- test/specs/platform.basic.tests.js | 152 +- test/specs/platform.dom.tests.js | 848 ++-- test/specs/plugin.filler.tests.js | 474 +-- test/specs/plugin.legend.tests.js | 1772 ++++----- test/specs/plugin.title.tests.js | 700 ++-- test/specs/plugin.tooltip.tests.js | 3144 +++++++-------- test/specs/scale.category.tests.js | 1062 ++--- test/specs/scale.linear.tests.js | 2356 +++++------ test/specs/scale.logarithmic.tests.js | 2324 +++++------ test/specs/scale.radialLinear.tests.js | 1170 +++--- test/specs/scale.time.tests.js | 2470 ++++++------ types/animation.d.ts | 30 +- types/index.esm.d.ts | 42 +- 365 files changed, 42950 insertions(+), 42954 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b00449ca2c..9c250497fa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2014,9 +2014,9 @@ } }, "eslint-config-chartjs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-chartjs/-/eslint-config-chartjs-0.2.0.tgz", - "integrity": "sha512-9NbZlmn5clHbzFc+EK7zp7KhgU2vhcbJNb4OrSrm9nMOMTs7yVdzP00jqqfs7dhDkhT4CfVxQlV1DMfjhGBLIA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-chartjs/-/eslint-config-chartjs-0.3.0.tgz", + "integrity": "sha512-L3AC5VSG8EBwwKkpOrxvBMjYzGF/XrGM+EjXgYO1KFUn3cMUFMKd562lSHdCSr4+ocw80vi+0fZhiFesUpqV3g==", "dev": true }, "eslint-plugin-es": { diff --git a/package.json b/package.json index d186333fa8b..b3a560cadbf 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "coveralls": "^3.1.0", "cross-env": "^7.0.3", "eslint": "^7.16.0", - "eslint-config-chartjs": "^0.2.0", + "eslint-config-chartjs": "^0.3.0", "eslint-plugin-es": "^4.1.0", "eslint-plugin-html": "^6.1.1", "glob": "^7.1.6", diff --git a/samples/.eslintrc.yml b/samples/.eslintrc.yml index bdf8eab325c..618932b79af 100644 --- a/samples/.eslintrc.yml +++ b/samples/.eslintrc.yml @@ -7,9 +7,5 @@ globals: randomScalingFactor: true rules: - no-new: 0 - no-var: 0 - object-shorthand: 0 - prefer-arrow-callback: 0 - no-invalid-this: 0 - no-unneeded-ternary: 0 + indent: ["error", "tab", {flatTernaryExpressions: true}] + no-new: "off" diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 24bd6756659..165421209d1 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -1,20 +1,20 @@ import DatasetController from '../core/core.datasetController'; import { - clipArea, unclipArea, _arrayUnique, isArray, isNullOrUndef, - valueOrDefault, resolveObjectKey, sign + clipArea, unclipArea, _arrayUnique, isArray, isNullOrUndef, + valueOrDefault, resolveObjectKey, sign } from '../helpers'; function getAllScaleValues(scale) { - if (!scale._cache.$bar) { - const metas = scale.getMatchingVisibleMetas('bar'); - let values = []; - - for (let i = 0, ilen = metas.length; i < ilen; i++) { - values = values.concat(metas[i].controller.getAllParsedValues(scale)); - } - scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b)); - } - return scale._cache.$bar; + if (!scale._cache.$bar) { + const metas = scale.getMatchingVisibleMetas('bar'); + let values = []; + + for (let i = 0, ilen = metas.length; i < ilen; i++) { + values = values.concat(metas[i].controller.getAllParsedValues(scale)); + } + scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b)); + } + return scale._cache.$bar; } /** @@ -22,25 +22,25 @@ function getAllScaleValues(scale) { * @private */ function computeMinSampleSize(scale) { - const values = getAllScaleValues(scale); - let min = scale._length; - let i, ilen, curr, prev; - const updateMinAndPrev = () => { - min = Math.min(min, i && Math.abs(curr - prev) || min); - prev = curr; - }; - - for (i = 0, ilen = values.length; i < ilen; ++i) { - curr = scale.getPixelForValue(values[i]); - updateMinAndPrev(); - } - - for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) { - curr = scale.getPixelForTick(i); - updateMinAndPrev(); - } - - return min; + const values = getAllScaleValues(scale); + let min = scale._length; + let i, ilen, curr, prev; + const updateMinAndPrev = () => { + min = Math.min(min, i && Math.abs(curr - prev) || min); + prev = curr; + }; + + for (i = 0, ilen = values.length; i < ilen; ++i) { + curr = scale.getPixelForValue(values[i]); + updateMinAndPrev(); + } + + for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + updateMinAndPrev(); + } + + return min; } /** @@ -50,25 +50,25 @@ function computeMinSampleSize(scale) { * @private */ function computeFitCategoryTraits(index, ruler, options, stackCount) { - const thickness = options.barThickness; - let size, ratio; - - if (isNullOrUndef(thickness)) { - size = ruler.min * options.categoryPercentage; - ratio = options.barPercentage; - } else { - // When bar thickness is enforced, category and bar percentages are ignored. - // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') - // and deprecate barPercentage since this value is ignored when thickness is absolute. - size = thickness * stackCount; - ratio = 1; - } - - return { - chunk: size / stackCount, - ratio, - start: ruler.pixels[index] - (size / 2) - }; + const thickness = options.barThickness; + let size, ratio; + + if (isNullOrUndef(thickness)) { + size = ruler.min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * stackCount; + ratio = 1; + } + + return { + chunk: size / stackCount, + ratio, + start: ruler.pixels[index] - (size / 2) + }; } /** @@ -78,427 +78,427 @@ function computeFitCategoryTraits(index, ruler, options, stackCount) { * @private */ function computeFlexCategoryTraits(index, ruler, options, stackCount) { - const pixels = ruler.pixels; - const curr = pixels[index]; - let prev = index > 0 ? pixels[index - 1] : null; - let next = index < pixels.length - 1 ? pixels[index + 1] : null; - const percent = options.categoryPercentage; - - if (prev === null) { - // first data: its size is double based on the next point or, - // if it's also the last data, we use the scale size. - prev = curr - (next === null ? ruler.end - ruler.start : next - curr); - } - - if (next === null) { - // last data: its size is also double based on the previous point. - next = curr + curr - prev; - } - - const start = curr - (curr - Math.min(prev, next)) / 2 * percent; - const size = Math.abs(next - prev) / 2 * percent; - - return { - chunk: size / stackCount, - ratio: options.barPercentage, - start - }; + const pixels = ruler.pixels; + const curr = pixels[index]; + let prev = index > 0 ? pixels[index - 1] : null; + let next = index < pixels.length - 1 ? pixels[index + 1] : null; + const percent = options.categoryPercentage; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + const start = curr - (curr - Math.min(prev, next)) / 2 * percent; + const size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / stackCount, + ratio: options.barPercentage, + start + }; } function parseFloatBar(entry, item, vScale, i) { - const startValue = vScale.parse(entry[0], i); - const endValue = vScale.parse(entry[1], i); - const min = Math.min(startValue, endValue); - const max = Math.max(startValue, endValue); - let barStart = min; - let barEnd = max; - - if (Math.abs(min) > Math.abs(max)) { - barStart = max; - barEnd = min; - } - - // Store `barEnd` (furthest away from origin) as parsed value, - // to make stacking straight forward - item[vScale.axis] = barEnd; - - item._custom = { - barStart, - barEnd, - start: startValue, - end: endValue, - min, - max - }; + const startValue = vScale.parse(entry[0], i); + const endValue = vScale.parse(entry[1], i); + const min = Math.min(startValue, endValue); + const max = Math.max(startValue, endValue); + let barStart = min; + let barEnd = max; + + if (Math.abs(min) > Math.abs(max)) { + barStart = max; + barEnd = min; + } + + // Store `barEnd` (furthest away from origin) as parsed value, + // to make stacking straight forward + item[vScale.axis] = barEnd; + + item._custom = { + barStart, + barEnd, + start: startValue, + end: endValue, + min, + max + }; } function parseValue(entry, item, vScale, i) { - if (isArray(entry)) { - parseFloatBar(entry, item, vScale, i); - } else { - item[vScale.axis] = vScale.parse(entry, i); - } - return item; + if (isArray(entry)) { + parseFloatBar(entry, item, vScale, i); + } else { + item[vScale.axis] = vScale.parse(entry, i); + } + return item; } function parseArrayOrPrimitive(meta, data, start, count) { - const iScale = meta.iScale; - const vScale = meta.vScale; - const labels = iScale.getLabels(); - const singleScale = iScale === vScale; - const parsed = []; - let i, ilen, item, entry; - - for (i = start, ilen = start + count; i < ilen; ++i) { - entry = data[i]; - item = {}; - item[iScale.axis] = singleScale || iScale.parse(labels[i], i); - parsed.push(parseValue(entry, item, vScale, i)); - } - return parsed; + const iScale = meta.iScale; + const vScale = meta.vScale; + const labels = iScale.getLabels(); + const singleScale = iScale === vScale; + const parsed = []; + let i, ilen, item, entry; + + for (i = start, ilen = start + count; i < ilen; ++i) { + entry = data[i]; + item = {}; + item[iScale.axis] = singleScale || iScale.parse(labels[i], i); + parsed.push(parseValue(entry, item, vScale, i)); + } + return parsed; } function isFloatBar(custom) { - return custom && custom.barStart !== undefined && custom.barEnd !== undefined; + return custom && custom.barStart !== undefined && custom.barEnd !== undefined; } export default class BarController extends DatasetController { - /** + /** * Overriding primitive data parsing since we support mixed primitive/array * data for float bars * @protected */ - parsePrimitiveData(meta, data, start, count) { - return parseArrayOrPrimitive(meta, data, start, count); - } + parsePrimitiveData(meta, data, start, count) { + return parseArrayOrPrimitive(meta, data, start, count); + } - /** + /** * Overriding array data parsing since we support mixed primitive/array * data for float bars * @protected */ - parseArrayData(meta, data, start, count) { - return parseArrayOrPrimitive(meta, data, start, count); - } + parseArrayData(meta, data, start, count) { + return parseArrayOrPrimitive(meta, data, start, count); + } - /** + /** * Overriding object data parsing since we support mixed primitive/array * value-scale data for float bars * @protected */ - parseObjectData(meta, data, start, count) { - const {iScale, vScale} = meta; - const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; - const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey; - const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey; - const parsed = []; - let i, ilen, item, obj; - for (i = start, ilen = start + count; i < ilen; ++i) { - obj = data[i]; - item = {}; - item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i); - parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i)); - } - return parsed; - } - - /** + parseObjectData(meta, data, start, count) { + const {iScale, vScale} = meta; + const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; + const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey; + const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey; + const parsed = []; + let i, ilen, item, obj; + for (i = start, ilen = start + count; i < ilen; ++i) { + obj = data[i]; + item = {}; + item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i); + parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i)); + } + return parsed; + } + + /** * @protected */ - updateRangeFromParsed(range, scale, parsed, stack) { - super.updateRangeFromParsed(range, scale, parsed, stack); - const custom = parsed._custom; - if (custom && scale === this._cachedMeta.vScale) { - // float bar: only one end of the bar is considered by `super` - range.min = Math.min(range.min, custom.min); - range.max = Math.max(range.max, custom.max); - } - } - - /** + updateRangeFromParsed(range, scale, parsed, stack) { + super.updateRangeFromParsed(range, scale, parsed, stack); + const custom = parsed._custom; + if (custom && scale === this._cachedMeta.vScale) { + // float bar: only one end of the bar is considered by `super` + range.min = Math.min(range.min, custom.min); + range.max = Math.max(range.max, custom.max); + } + } + + /** * @protected */ - getLabelAndValue(index) { - const me = this; - const meta = me._cachedMeta; - const {iScale, vScale} = meta; - const parsed = me.getParsed(index); - const custom = parsed._custom; - const value = isFloatBar(custom) - ? '[' + custom.start + ', ' + custom.end + ']' - : '' + vScale.getLabelForValue(parsed[vScale.axis]); - - return { - label: '' + iScale.getLabelForValue(parsed[iScale.axis]), - value - }; - } - - initialize() { - const me = this; - me.enableOptionSharing = true; - - super.initialize(); - - const meta = me._cachedMeta; - meta.stack = me.getDataset().stack; - } - - update(mode) { - const me = this; - const meta = me._cachedMeta; - - me.updateElements(meta.data, 0, meta.data.length, mode); - } - - updateElements(bars, start, count, mode) { - const me = this; - const reset = mode === 'reset'; - const vscale = me._cachedMeta.vScale; - const base = vscale.getBasePixel(); - const horizontal = vscale.isHorizontal(); - const ruler = me._getRuler(); - const firstOpts = me.resolveDataElementOptions(start, mode); - const sharedOptions = me.getSharedOptions(firstOpts); - const includeOptions = me.includeOptions(mode, sharedOptions); - - me.updateSharedOptions(sharedOptions, mode, firstOpts); - - for (let i = start; i < start + count; i++) { - const options = sharedOptions || me.resolveDataElementOptions(i, mode); - const vpixels = me._calculateBarValuePixels(i, options); - const ipixels = me._calculateBarIndexPixels(i, ruler, options); - - const properties = { - horizontal, - base: reset ? base : vpixels.base, - x: horizontal ? reset ? base : vpixels.head : ipixels.center, - y: horizontal ? ipixels.center : reset ? base : vpixels.head, - height: horizontal ? ipixels.size : undefined, - width: horizontal ? undefined : ipixels.size - }; - - if (includeOptions) { - properties.options = options; - } - me.updateElement(bars[i], i, properties, mode); - } - } - - /** + getLabelAndValue(index) { + const me = this; + const meta = me._cachedMeta; + const {iScale, vScale} = meta; + const parsed = me.getParsed(index); + const custom = parsed._custom; + const value = isFloatBar(custom) + ? '[' + custom.start + ', ' + custom.end + ']' + : '' + vScale.getLabelForValue(parsed[vScale.axis]); + + return { + label: '' + iScale.getLabelForValue(parsed[iScale.axis]), + value + }; + } + + initialize() { + const me = this; + me.enableOptionSharing = true; + + super.initialize(); + + const meta = me._cachedMeta; + meta.stack = me.getDataset().stack; + } + + update(mode) { + const me = this; + const meta = me._cachedMeta; + + me.updateElements(meta.data, 0, meta.data.length, mode); + } + + updateElements(bars, start, count, mode) { + const me = this; + const reset = mode === 'reset'; + const vscale = me._cachedMeta.vScale; + const base = vscale.getBasePixel(); + const horizontal = vscale.isHorizontal(); + const ruler = me._getRuler(); + const firstOpts = me.resolveDataElementOptions(start, mode); + const sharedOptions = me.getSharedOptions(firstOpts); + const includeOptions = me.includeOptions(mode, sharedOptions); + + me.updateSharedOptions(sharedOptions, mode, firstOpts); + + for (let i = start; i < start + count; i++) { + const options = sharedOptions || me.resolveDataElementOptions(i, mode); + const vpixels = me._calculateBarValuePixels(i, options); + const ipixels = me._calculateBarIndexPixels(i, ruler, options); + + const properties = { + horizontal, + base: reset ? base : vpixels.base, + x: horizontal ? reset ? base : vpixels.head : ipixels.center, + y: horizontal ? ipixels.center : reset ? base : vpixels.head, + height: horizontal ? ipixels.size : undefined, + width: horizontal ? undefined : ipixels.size + }; + + if (includeOptions) { + properties.options = options; + } + me.updateElement(bars[i], i, properties, mode); + } + } + + /** * Returns the stacks based on groups and bar visibility. * @param {number} [last] - The dataset index * @param {number} [dataIndex] - The data index of the ruler * @returns {string[]} The list of stack IDs * @private */ - _getStacks(last, dataIndex) { - const me = this; - const meta = me._cachedMeta; - const iScale = meta.iScale; - const metasets = iScale.getMatchingVisibleMetas(me._type); - const stacked = iScale.options.stacked; - const ilen = metasets.length; - const stacks = []; - let i, item; - - for (i = 0; i < ilen; ++i) { - item = metasets[i]; - - if (typeof dataIndex !== 'undefined') { - const val = item.controller.getParsed(dataIndex)[ - item.controller._cachedMeta.vScale.axis - ]; - - if (isNullOrUndef(val) || isNaN(val)) { - continue; - } - } - - // stacked | meta.stack - // | found | not found | undefined - // false | x | x | x - // true | | x | - // undefined | | x | x - if (stacked === false || stacks.indexOf(item.stack) === -1 || + _getStacks(last, dataIndex) { + const me = this; + const meta = me._cachedMeta; + const iScale = meta.iScale; + const metasets = iScale.getMatchingVisibleMetas(me._type); + const stacked = iScale.options.stacked; + const ilen = metasets.length; + const stacks = []; + let i, item; + + for (i = 0; i < ilen; ++i) { + item = metasets[i]; + + if (typeof dataIndex !== 'undefined') { + const val = item.controller.getParsed(dataIndex)[ + item.controller._cachedMeta.vScale.axis + ]; + + if (isNullOrUndef(val) || isNaN(val)) { + continue; + } + } + + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(item.stack) === -1 || (stacked === undefined && item.stack === undefined)) { - stacks.push(item.stack); - } - if (item.index === last) { - break; - } - } - - // No stacks? that means there is no visible data. Let's still initialize an `undefined` - // stack where possible invisible bars will be located. - // https://github.com/chartjs/Chart.js/issues/6368 - if (!stacks.length) { - stacks.push(undefined); - } - - return stacks; - } - - /** + stacks.push(item.stack); + } + if (item.index === last) { + break; + } + } + + // No stacks? that means there is no visible data. Let's still initialize an `undefined` + // stack where possible invisible bars will be located. + // https://github.com/chartjs/Chart.js/issues/6368 + if (!stacks.length) { + stacks.push(undefined); + } + + return stacks; + } + + /** * Returns the effective number of stacks based on groups and bar visibility. * @private */ - _getStackCount(index) { - return this._getStacks(undefined, index).length; - } + _getStackCount(index) { + return this._getStacks(undefined, index).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(datasetIndex, name) { - const stacks = this._getStacks(datasetIndex); - const index = (name !== undefined) - ? stacks.indexOf(name) - : -1; // indexOf returns -1 if element is not present - - return (index === -1) - ? stacks.length - 1 - : index; - } - - /** + _getStackIndex(datasetIndex, name) { + const stacks = this._getStacks(datasetIndex); + const index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + } + + /** * @private */ - _getRuler() { - const me = this; - const meta = me._cachedMeta; - const iScale = meta.iScale; - const pixels = []; - let i, ilen; - - for (i = 0, ilen = meta.data.length; i < ilen; ++i) { - pixels.push(iScale.getPixelForValue(me.getParsed(i)[iScale.axis], i)); - } - - // Note: a potential optimization would be to skip computing this - // only if the barThickness option is defined - // Since a scriptable option may return null or undefined that - // means the option would have to be of type number - const min = computeMinSampleSize(iScale); - - return { - min, - pixels, - start: iScale._startPixel, - end: iScale._endPixel, - stackCount: me._getStackCount(), - scale: iScale - }; - } - - /** + _getRuler() { + const me = this; + const meta = me._cachedMeta; + const iScale = meta.iScale; + const pixels = []; + let i, ilen; + + for (i = 0, ilen = meta.data.length; i < ilen; ++i) { + pixels.push(iScale.getPixelForValue(me.getParsed(i)[iScale.axis], i)); + } + + // Note: a potential optimization would be to skip computing this + // only if the barThickness option is defined + // Since a scriptable option may return null or undefined that + // means the option would have to be of type number + const min = computeMinSampleSize(iScale); + + return { + min, + pixels, + start: iScale._startPixel, + end: iScale._endPixel, + stackCount: me._getStackCount(), + scale: iScale + }; + } + + /** * Note: pixel values are not clamped to the scale area. * @private */ - _calculateBarValuePixels(index, options) { - const me = this; - const meta = me._cachedMeta; - const vScale = meta.vScale; - const {base: baseValue, minBarLength} = options; - const parsed = me.getParsed(index); - const custom = parsed._custom; - const floating = isFloatBar(custom); - let value = parsed[vScale.axis]; - let start = 0; - let length = meta._stacked ? me.applyStack(vScale, parsed) : value; - let head, size; - - if (length !== value) { - start = length - value; - length = value; - } - - if (floating) { - value = custom.barStart; - length = custom.barEnd - custom.barStart; - // bars crossing origin are not stacked - if (value !== 0 && sign(value) !== sign(custom.barEnd)) { - start = 0; - } - start += value; - } - - const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start; - let base = vScale.getPixelForValue(startValue); - - if (this.chart.getDataVisibility(index)) { - head = vScale.getPixelForValue(start + length); - } else { - // When not visible, no height - head = base; - } - - size = head - base; - - if (minBarLength !== undefined && Math.abs(size) < minBarLength) { - size = size < 0 ? -minBarLength : minBarLength; - if (value === 0) { - base -= size / 2; - } - head = base + size; - } - - return { - size, - base, - head, - center: head + size / 2 - }; - } - - /** + _calculateBarValuePixels(index, options) { + const me = this; + const meta = me._cachedMeta; + const vScale = meta.vScale; + const {base: baseValue, minBarLength} = options; + const parsed = me.getParsed(index); + const custom = parsed._custom; + const floating = isFloatBar(custom); + let value = parsed[vScale.axis]; + let start = 0; + let length = meta._stacked ? me.applyStack(vScale, parsed) : value; + let head, size; + + if (length !== value) { + start = length - value; + length = value; + } + + if (floating) { + value = custom.barStart; + length = custom.barEnd - custom.barStart; + // bars crossing origin are not stacked + if (value !== 0 && sign(value) !== sign(custom.barEnd)) { + start = 0; + } + start += value; + } + + const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start; + let base = vScale.getPixelForValue(startValue); + + if (this.chart.getDataVisibility(index)) { + head = vScale.getPixelForValue(start + length); + } else { + // When not visible, no height + head = base; + } + + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = size < 0 ? -minBarLength : minBarLength; + if (value === 0) { + base -= size / 2; + } + head = base + size; + } + + return { + size, + base, + head, + center: head + size / 2 + }; + } + + /** * @private */ - _calculateBarIndexPixels(index, ruler, options) { - const me = this; - const stackCount = me.chart.options.skipNull ? me._getStackCount(index) : ruler.stackCount; - const range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options, stackCount) - : computeFitCategoryTraits(index, ruler, options, stackCount); - - const stackIndex = me._getStackIndex(me.index, me._cachedMeta.stack); - const center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); - const size = Math.min( - valueOrDefault(options.maxBarThickness, Infinity), - range.chunk * range.ratio); - - return { - base: center - size / 2, - head: center + size / 2, - center, - size - }; - } - - draw() { - const me = this; - const chart = me.chart; - const meta = me._cachedMeta; - const vScale = meta.vScale; - const rects = meta.data; - const ilen = rects.length; - let i = 0; - - clipArea(chart.ctx, chart.chartArea); - - for (; i < ilen; ++i) { - if (!isNaN(me.getParsed(i)[vScale.axis])) { - rects[i].draw(me._ctx); - } - } - - unclipArea(chart.ctx); - } + _calculateBarIndexPixels(index, ruler, options) { + const me = this; + const stackCount = me.chart.options.skipNull ? me._getStackCount(index) : ruler.stackCount; + const range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options, stackCount) + : computeFitCategoryTraits(index, ruler, options, stackCount); + + const stackIndex = me._getStackIndex(me.index, me._cachedMeta.stack); + const center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + const size = Math.min( + valueOrDefault(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center, + size + }; + } + + draw() { + const me = this; + const chart = me.chart; + const meta = me._cachedMeta; + const vScale = meta.vScale; + const rects = meta.data; + const ilen = rects.length; + let i = 0; + + clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + if (!isNaN(me.getParsed(i)[vScale.axis])) { + rects[i].draw(me._ctx); + } + } + + unclipArea(chart.ctx); + } } @@ -508,50 +508,50 @@ BarController.id = 'bar'; * @type {any} */ BarController.defaults = { - datasetElementType: false, - dataElementType: 'bar', - dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderSkipped', - 'borderWidth', - 'borderRadius', - 'barPercentage', - 'barThickness', - 'base', - 'categoryPercentage', - 'maxBarThickness', - 'minBarLength', - 'pointStyle' - ], - interaction: { - mode: 'index' - }, - - hover: {}, - - datasets: { - categoryPercentage: 0.8, - barPercentage: 0.9, - animation: { - numbers: { - type: 'number', - properties: ['x', 'y', 'base', 'width', 'height'] - } - } - }, - - scales: { - _index_: { - type: 'category', - offset: true, - gridLines: { - offsetGridLines: true - } - }, - _value_: { - type: 'linear', - beginAtZero: true, - } - } + datasetElementType: false, + dataElementType: 'bar', + dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'borderRadius', + 'barPercentage', + 'barThickness', + 'base', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength', + 'pointStyle' + ], + interaction: { + mode: 'index' + }, + + hover: {}, + + datasets: { + categoryPercentage: 0.8, + barPercentage: 0.9, + animation: { + numbers: { + type: 'number', + properties: ['x', 'y', 'base', 'width', 'height'] + } + } + }, + + scales: { + _index_: { + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }, + _value_: { + type: 'linear', + beginAtZero: true, + } + } }; diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index d4b0e186c16..7c8c7dcd7af 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -3,136 +3,136 @@ import {resolve} from '../helpers/helpers.options'; import {resolveObjectKey} from '../helpers/helpers.core'; export default class BubbleController extends DatasetController { - initialize() { - this.enableOptionSharing = true; - super.initialize(); - } + initialize() { + this.enableOptionSharing = true; + super.initialize(); + } - /** + /** * Parse array of objects * @protected */ - parseObjectData(meta, data, start, count) { - const {xScale, yScale} = meta; - const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; - const parsed = []; - let i, ilen, item; - for (i = start, ilen = start + count; i < ilen; ++i) { - item = data[i]; - parsed.push({ - x: xScale.parse(resolveObjectKey(item, xAxisKey), i), - y: yScale.parse(resolveObjectKey(item, yAxisKey), i), - _custom: item && item.r && +item.r - }); - } - return parsed; - } - - /** + parseObjectData(meta, data, start, count) { + const {xScale, yScale} = meta; + const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; + const parsed = []; + let i, ilen, item; + for (i = start, ilen = start + count; i < ilen; ++i) { + item = data[i]; + parsed.push({ + x: xScale.parse(resolveObjectKey(item, xAxisKey), i), + y: yScale.parse(resolveObjectKey(item, yAxisKey), i), + _custom: item && item.r && +item.r + }); + } + return parsed; + } + + /** * @protected */ - getMaxOverflow() { - const me = this; - const meta = me._cachedMeta; - const data = meta.data; - let max = 0; - for (let i = data.length - 1; i >= 0; --i) { - max = Math.max(max, data[i].size()); - } - return max > 0 && max; - } - - /** + getMaxOverflow() { + const me = this; + const meta = me._cachedMeta; + const data = meta.data; + let max = 0; + for (let i = data.length - 1; i >= 0; --i) { + max = Math.max(max, data[i].size()); + } + return max > 0 && max; + } + + /** * @protected */ - getLabelAndValue(index) { - const me = this; - const meta = me._cachedMeta; - const {xScale, yScale} = meta; - const parsed = me.getParsed(index); - const x = xScale.getLabelForValue(parsed.x); - const y = yScale.getLabelForValue(parsed.y); - const r = parsed._custom; - - return { - label: meta.label, - value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')' - }; - } - - update(mode) { - const me = this; - const points = me._cachedMeta.data; - - // Update Points - me.updateElements(points, 0, points.length, mode); - } - - updateElements(points, start, count, mode) { - const me = this; - const reset = mode === 'reset'; - const {xScale, yScale} = me._cachedMeta; - const firstOpts = me.resolveDataElementOptions(start, mode); - const sharedOptions = me.getSharedOptions(firstOpts); - const includeOptions = me.includeOptions(mode, sharedOptions); - - for (let i = start; i < start + count; i++) { - const point = points[i]; - const parsed = !reset && me.getParsed(i); - const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed.x); - const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed.y); - const properties = { - x, - y, - skip: isNaN(x) || isNaN(y) - }; - - if (includeOptions) { - properties.options = me.resolveDataElementOptions(i, mode); - - if (reset) { - properties.options.radius = 0; - } - } - - me.updateElement(point, i, properties, mode); - } - - me.updateSharedOptions(sharedOptions, mode, firstOpts); - } - - /** + getLabelAndValue(index) { + const me = this; + const meta = me._cachedMeta; + const {xScale, yScale} = meta; + const parsed = me.getParsed(index); + const x = xScale.getLabelForValue(parsed.x); + const y = yScale.getLabelForValue(parsed.y); + const r = parsed._custom; + + return { + label: meta.label, + value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')' + }; + } + + update(mode) { + const me = this; + const points = me._cachedMeta.data; + + // Update Points + me.updateElements(points, 0, points.length, mode); + } + + updateElements(points, start, count, mode) { + const me = this; + const reset = mode === 'reset'; + const {xScale, yScale} = me._cachedMeta; + const firstOpts = me.resolveDataElementOptions(start, mode); + const sharedOptions = me.getSharedOptions(firstOpts); + const includeOptions = me.includeOptions(mode, sharedOptions); + + for (let i = start; i < start + count; i++) { + const point = points[i]; + const parsed = !reset && me.getParsed(i); + const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed.x); + const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed.y); + const properties = { + x, + y, + skip: isNaN(x) || isNaN(y) + }; + + if (includeOptions) { + properties.options = me.resolveDataElementOptions(i, mode); + + if (reset) { + properties.options.radius = 0; + } + } + + me.updateElement(point, i, properties, mode); + } + + me.updateSharedOptions(sharedOptions, mode, firstOpts); + } + + /** * @param {number} index * @param {string} [mode] * @protected */ - resolveDataElementOptions(index, mode) { - const me = this; - const chart = me.chart; - const parsed = me.getParsed(index); - let values = super.resolveDataElementOptions(index, mode); - - // Scriptable options - const context = me.getContext(index, mode === 'active'); - - // In case values were cached (and thus frozen), we need to clone the values - if (values.$shared) { - values = Object.assign({}, values, {$shared: false}); - } - - - // Custom radius resolution - if (mode !== 'active') { - values.radius = 0; - } - values.radius += resolve([ - parsed && parsed._custom, - me._config.radius, - chart.options.elements.point.radius - ], context, index); - - return values; - } + resolveDataElementOptions(index, mode) { + const me = this; + const chart = me.chart; + const parsed = me.getParsed(index); + let values = super.resolveDataElementOptions(index, mode); + + // Scriptable options + const context = me.getContext(index, mode === 'active'); + + // In case values were cached (and thus frozen), we need to clone the values + if (values.$shared) { + values = Object.assign({}, values, {$shared: false}); + } + + + // Custom radius resolution + if (mode !== 'active') { + values.radius = 0; + } + values.radius += resolve([ + parsed && parsed._custom, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } } BubbleController.id = 'bubble'; @@ -141,38 +141,38 @@ BubbleController.id = 'bubble'; * @type {any} */ BubbleController.defaults = { - datasetElementType: false, - dataElementType: 'point', - dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'hitRadius', - 'radius', - 'pointStyle', - 'rotation' - ], - animation: { - numbers: { - properties: ['x', 'y', 'borderWidth', 'radius'] - } - }, - scales: { - x: { - type: 'linear' - }, - y: { - type: 'linear' - } - }, - plugins: { - tooltip: { - callbacks: { - title() { - // Title doesn't make sense for scatter since we format the data as a point - return ''; - } - } - } - } + datasetElementType: false, + dataElementType: 'point', + dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hitRadius', + 'radius', + 'pointStyle', + 'rotation' + ], + animation: { + numbers: { + properties: ['x', 'y', 'borderWidth', 'radius'] + } + }, + scales: { + x: { + type: 'linear' + }, + y: { + type: 'linear' + } + }, + plugins: { + tooltip: { + callbacks: { + title() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + } + } + } + } }; diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 2374fcd0e45..24e242fa30e 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -9,315 +9,315 @@ import {toRadians, PI, TAU, HALF_PI} from '../helpers/helpers.math'; function getRatioAndOffset(rotation, circumference, cutout) { - let ratioX = 1; - let ratioY = 1; - let offsetX = 0; - let offsetY = 0; - // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc - if (circumference < TAU) { - let startAngle = rotation % TAU; - startAngle += startAngle >= PI ? -TAU : startAngle < -PI ? TAU : 0; - const endAngle = startAngle + circumference; - const startX = Math.cos(startAngle); - const startY = Math.sin(startAngle); - const endX = Math.cos(endAngle); - const endY = Math.sin(endAngle); - const contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= TAU; - const contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= TAU + HALF_PI; - const contains180 = startAngle === -PI || endAngle >= PI; - const contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI; - const minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); - const minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); - const maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); - const maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); - ratioX = (maxX - minX) / 2; - ratioY = (maxY - minY) / 2; - offsetX = -(maxX + minX) / 2; - offsetY = -(maxY + minY) / 2; - } - return {ratioX, ratioY, offsetX, offsetY}; + let ratioX = 1; + let ratioY = 1; + let offsetX = 0; + let offsetY = 0; + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < TAU) { + let startAngle = rotation % TAU; + startAngle += startAngle >= PI ? -TAU : startAngle < -PI ? TAU : 0; + const endAngle = startAngle + circumference; + const startX = Math.cos(startAngle); + const startY = Math.sin(startAngle); + const endX = Math.cos(endAngle); + const endY = Math.sin(endAngle); + const contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= TAU; + const contains90 = (startAngle <= HALF_PI && endAngle >= HALF_PI) || endAngle >= TAU + HALF_PI; + const contains180 = startAngle === -PI || endAngle >= PI; + const contains270 = (startAngle <= -HALF_PI && endAngle >= -HALF_PI) || endAngle >= PI + HALF_PI; + const minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + const minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + const maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + const maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + return {ratioX, ratioY, offsetX, offsetY}; } export default class DoughnutController extends DatasetController { - constructor(chart, datasetIndex) { - super(chart, datasetIndex); + constructor(chart, datasetIndex) { + super(chart, datasetIndex); - this.enableOptionSharing = true; - this.innerRadius = undefined; - this.outerRadius = undefined; - this.offsetX = undefined; - this.offsetY = undefined; - } + this.enableOptionSharing = true; + this.innerRadius = undefined; + this.outerRadius = undefined; + this.offsetX = undefined; + this.offsetY = undefined; + } - linkScales() {} + linkScales() {} - /** + /** * Override data parsing, since we are not using scales */ - parse(start, count) { - const data = this.getDataset().data; - const meta = this._cachedMeta; - let i, ilen; - for (i = start, ilen = start + count; i < ilen; ++i) { - meta._parsed[i] = +data[i]; - } - } - - // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly - getRingIndex(datasetIndex) { - let ringIndex = 0; - - for (let j = 0; j < datasetIndex; ++j) { - if (this.chart.isDatasetVisible(j)) { - ++ringIndex; - } - } - - return ringIndex; - } - - /** + parse(start, count) { + const data = this.getDataset().data; + const meta = this._cachedMeta; + let i, ilen; + for (i = start, ilen = start + count; i < ilen; ++i) { + meta._parsed[i] = +data[i]; + } + } + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex(datasetIndex) { + let ringIndex = 0; + + for (let j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + } + + /** * @private */ - _getRotation() { - return toRadians(valueOrDefault(this._config.rotation, this.chart.options.rotation) - 90); - } + _getRotation() { + return toRadians(valueOrDefault(this._config.rotation, this.chart.options.rotation) - 90); + } - /** + /** * @private */ - _getCircumference() { - return toRadians(valueOrDefault(this._config.circumference, this.chart.options.circumference)); - } + _getCircumference() { + return toRadians(valueOrDefault(this._config.circumference, this.chart.options.circumference)); + } - /** + /** * Get the maximal rotation & circumference extents * across all visible datasets. */ - _getRotationExtents() { - let min = TAU; - let max = -TAU; - - const me = this; - - for (let i = 0; i < me.chart.data.datasets.length; ++i) { - if (me.chart.isDatasetVisible(i)) { - const controller = me.chart.getDatasetMeta(i).controller; - const rotation = controller._getRotation(); - const circumference = controller._getCircumference(); - - min = Math.min(min, rotation); - max = Math.max(max, rotation + circumference); - } - } - - return { - rotation: min, - circumference: max - min, - }; - } - - /** + _getRotationExtents() { + let min = TAU; + let max = -TAU; + + const me = this; + + for (let i = 0; i < me.chart.data.datasets.length; ++i) { + if (me.chart.isDatasetVisible(i)) { + const controller = me.chart.getDatasetMeta(i).controller; + const rotation = controller._getRotation(); + const circumference = controller._getCircumference(); + + min = Math.min(min, rotation); + max = Math.max(max, rotation + circumference); + } + } + + return { + rotation: min, + circumference: max - min, + }; + } + + /** * @param {string} mode */ - update(mode) { - const me = this; - const chart = me.chart; - const {chartArea, options} = chart; - const meta = me._cachedMeta; - const arcs = meta.data; - const cutout = options.cutoutPercentage / 100 || 0; - const chartWeight = me._getRingWeight(me.index); - - // Compute the maximal rotation & circumference limits. - // If we only consider our dataset, this can cause problems when two datasets - // are both less than a circle with different rotations (starting angles) - const {circumference, rotation} = me._getRotationExtents(); - const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout); - const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs); - const maxWidth = (chartArea.right - chartArea.left - spacing) / ratioX; - const maxHeight = (chartArea.bottom - chartArea.top - spacing) / ratioY; - const outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); - const innerRadius = Math.max(outerRadius * cutout, 0); - const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal(); - me.offsetX = offsetX * outerRadius; - me.offsetY = offsetY * outerRadius; - - meta.total = me.calculateTotal(); - - me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index); - me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0); - - me.updateElements(arcs, 0, arcs.length, mode); - } - - /** + update(mode) { + const me = this; + const chart = me.chart; + const {chartArea, options} = chart; + const meta = me._cachedMeta; + const arcs = meta.data; + const cutout = options.cutoutPercentage / 100 || 0; + const chartWeight = me._getRingWeight(me.index); + + // Compute the maximal rotation & circumference limits. + // If we only consider our dataset, this can cause problems when two datasets + // are both less than a circle with different rotations (starting angles) + const {circumference, rotation} = me._getRotationExtents(); + const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout); + const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs); + const maxWidth = (chartArea.right - chartArea.left - spacing) / ratioX; + const maxHeight = (chartArea.bottom - chartArea.top - spacing) / ratioY; + const outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + const innerRadius = Math.max(outerRadius * cutout, 0); + const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal(); + me.offsetX = offsetX * outerRadius; + me.offsetY = offsetY * outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0); + + me.updateElements(arcs, 0, arcs.length, mode); + } + + /** * @private */ - _circumference(i, reset) { - const me = this; - const opts = me.chart.options; - const meta = me._cachedMeta; - const circumference = me._getCircumference(); - return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * circumference / TAU) : 0; - } - - updateElements(arcs, start, count, mode) { - const me = this; - const reset = mode === 'reset'; - const chart = me.chart; - const chartArea = chart.chartArea; - const opts = chart.options; - const animationOpts = opts.animation; - const centerX = (chartArea.left + chartArea.right) / 2; - const centerY = (chartArea.top + chartArea.bottom) / 2; - const animateScale = reset && animationOpts.animateScale; - const innerRadius = animateScale ? 0 : me.innerRadius; - const outerRadius = animateScale ? 0 : me.outerRadius; - const firstOpts = me.resolveDataElementOptions(start, mode); - const sharedOptions = me.getSharedOptions(firstOpts); - const includeOptions = me.includeOptions(mode, sharedOptions); - let startAngle = me._getRotation(); - let i; - - for (i = 0; i < start; ++i) { - startAngle += me._circumference(i, reset); - } - - for (i = start; i < start + count; ++i) { - const circumference = me._circumference(i, reset); - const arc = arcs[i]; - const properties = { - x: centerX + me.offsetX, - y: centerY + me.offsetY, - startAngle, - endAngle: startAngle + circumference, - circumference, - outerRadius, - innerRadius - }; - if (includeOptions) { - properties.options = sharedOptions || me.resolveDataElementOptions(i, mode); - } - startAngle += circumference; - - me.updateElement(arc, i, properties, mode); - } - me.updateSharedOptions(sharedOptions, mode, firstOpts); - } - - calculateTotal() { - const meta = this._cachedMeta; - const metaData = meta.data; - let total = 0; - let i; - - for (i = 0; i < metaData.length; i++) { - const value = meta._parsed[i]; - if (!isNaN(value) && this.chart.getDataVisibility(i)) { - total += Math.abs(value); - } - } - - return total; - } - - calculateCircumference(value) { - const total = this._cachedMeta.total; - if (total > 0 && !isNaN(value)) { - return TAU * (Math.abs(value) / total); - } - return 0; - } - - getLabelAndValue(index) { - const me = this; - const meta = me._cachedMeta; - const chart = me.chart; - const labels = chart.data.labels || []; - const value = formatNumber(meta._parsed[index], chart.options.locale); - - return { - label: labels[index] || '', - value, - }; - } - - getMaxBorderWidth(arcs) { - const me = this; - let max = 0; - const chart = me.chart; - let i, ilen, meta, controller, options; - - if (!arcs) { - // Find the outmost visible dataset - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - meta = chart.getDatasetMeta(i); - arcs = meta.data; - controller = meta.controller; - if (controller !== me) { - controller.configure(); - } - break; - } - } - } - - if (!arcs) { - return 0; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - options = controller.resolveDataElementOptions(i); - if (options.borderAlign !== 'inner') { - max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0); - } - } - return max; - } - - getMaxOffset(arcs) { - let max = 0; - - for (let i = 0, ilen = arcs.length; i < ilen; ++i) { - const options = this.resolveDataElementOptions(i); - max = Math.max(max, options.offset || 0, options.hoverOffset || 0); - } - return max; - } - - /** + _circumference(i, reset) { + const me = this; + const opts = me.chart.options; + const meta = me._cachedMeta; + const circumference = me._getCircumference(); + return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * circumference / TAU) : 0; + } + + updateElements(arcs, start, count, mode) { + const me = this; + const reset = mode === 'reset'; + const chart = me.chart; + const chartArea = chart.chartArea; + const opts = chart.options; + const animationOpts = opts.animation; + const centerX = (chartArea.left + chartArea.right) / 2; + const centerY = (chartArea.top + chartArea.bottom) / 2; + const animateScale = reset && animationOpts.animateScale; + const innerRadius = animateScale ? 0 : me.innerRadius; + const outerRadius = animateScale ? 0 : me.outerRadius; + const firstOpts = me.resolveDataElementOptions(start, mode); + const sharedOptions = me.getSharedOptions(firstOpts); + const includeOptions = me.includeOptions(mode, sharedOptions); + let startAngle = me._getRotation(); + let i; + + for (i = 0; i < start; ++i) { + startAngle += me._circumference(i, reset); + } + + for (i = start; i < start + count; ++i) { + const circumference = me._circumference(i, reset); + const arc = arcs[i]; + const properties = { + x: centerX + me.offsetX, + y: centerY + me.offsetY, + startAngle, + endAngle: startAngle + circumference, + circumference, + outerRadius, + innerRadius + }; + if (includeOptions) { + properties.options = sharedOptions || me.resolveDataElementOptions(i, mode); + } + startAngle += circumference; + + me.updateElement(arc, i, properties, mode); + } + me.updateSharedOptions(sharedOptions, mode, firstOpts); + } + + calculateTotal() { + const meta = this._cachedMeta; + const metaData = meta.data; + let total = 0; + let i; + + for (i = 0; i < metaData.length; i++) { + const value = meta._parsed[i]; + if (!isNaN(value) && this.chart.getDataVisibility(i)) { + total += Math.abs(value); + } + } + + return total; + } + + calculateCircumference(value) { + const total = this._cachedMeta.total; + if (total > 0 && !isNaN(value)) { + return TAU * (Math.abs(value) / total); + } + return 0; + } + + getLabelAndValue(index) { + const me = this; + const meta = me._cachedMeta; + const chart = me.chart; + const labels = chart.data.labels || []; + const value = formatNumber(meta._parsed[index], chart.options.locale); + + return { + label: labels[index] || '', + value, + }; + } + + getMaxBorderWidth(arcs) { + const me = this; + let max = 0; + const chart = me.chart; + let i, ilen, meta, controller, options; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + controller = meta.controller; + if (controller !== me) { + controller.configure(); + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + options = controller.resolveDataElementOptions(i); + if (options.borderAlign !== 'inner') { + max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0); + } + } + return max; + } + + getMaxOffset(arcs) { + let max = 0; + + for (let i = 0, ilen = arcs.length; i < ilen; ++i) { + const options = this.resolveDataElementOptions(i); + max = Math.max(max, options.offset || 0, options.hoverOffset || 0); + } + return max; + } + + /** * 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(datasetIndex) { - let ringWeightOffset = 0; + _getRingWeightOffset(datasetIndex) { + let ringWeightOffset = 0; - for (let i = 0; i < datasetIndex; ++i) { - if (this.chart.isDatasetVisible(i)) { - ringWeightOffset += this._getRingWeight(i); - } - } + for (let i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } - return ringWeightOffset; - } + return ringWeightOffset; + } - /** + /** * @private */ - _getRingWeight(datasetIndex) { - return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0); - } + _getRingWeight(datasetIndex) { + return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0); + } - /** + /** * Returns the sum of all visible data set weights. * @private */ - _getVisibleDatasetWeightTotal() { - return this._getRingWeightOffset(this.chart.data.datasets.length) || 1; - } + _getVisibleDatasetWeightTotal() { + return this._getRingWeightOffset(this.chart.data.datasets.length) || 1; + } } DoughnutController.id = 'doughnut'; @@ -326,89 +326,89 @@ DoughnutController.id = 'doughnut'; * @type {any} */ DoughnutController.defaults = { - datasetElementType: false, - dataElementType: 'arc', - dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'offset' - ], - animation: { - numbers: { - type: 'number', - properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth'] - }, - // Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - // Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false - }, - aspectRatio: 1, - - // The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, - - // The rotation of the chart, where the first data arc begins. - rotation: 0, - - // The total circumference of the chart. - circumference: 360, - - // Need to override these to give a nice default - plugins: { - legend: { - labels: { - generateLabels(chart) { - const data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map((label, i) => { - const meta = chart.getDatasetMeta(0); - const style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: !chart.getDataVisibility(i), - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick(e, legendItem, legend) { - legend.chart.toggleDataVisibility(legendItem.index); - legend.chart.update(); - } - }, - tooltip: { - callbacks: { - title() { - return ''; - }, - label(tooltipItem) { - let dataLabel = tooltipItem.label; - const value = ': ' + tooltipItem.formattedValue; - - if (isArray(dataLabel)) { - // show value on first line of multiline label - // need to clone because we are changing the value - dataLabel = dataLabel.slice(); - dataLabel[0] += value; - } else { - dataLabel += value; - } - - return dataLabel; - } - } - } - } + datasetElementType: false, + dataElementType: 'arc', + dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'offset' + ], + animation: { + numbers: { + type: 'number', + properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth'] + }, + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + aspectRatio: 1, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: 0, + + // The total circumference of the chart. + circumference: 360, + + // Need to override these to give a nice default + plugins: { + legend: { + labels: { + generateLabels(chart) { + const data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map((label, i) => { + const meta = chart.getDatasetMeta(0); + const style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: !chart.getDataVisibility(i), + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick(e, legendItem, legend) { + legend.chart.toggleDataVisibility(legendItem.index); + legend.chart.update(); + } + }, + tooltip: { + callbacks: { + title() { + return ''; + }, + label(tooltipItem) { + let dataLabel = tooltipItem.label; + const value = ': ' + tooltipItem.formattedValue; + + if (isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } + } }; diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 3690fa90dbc..ed3f9319ed1 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -6,123 +6,123 @@ import {_lookupByKey} from '../helpers/helpers.collection'; export default class LineController extends DatasetController { - initialize() { - this.enableOptionSharing = true; - super.initialize(); - } - - update(mode) { - const me = this; - const meta = me._cachedMeta; - const {dataset: line, data: points = []} = meta; - // @ts-ignore - const animationsDisabled = me.chart._animationsDisabled; - let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); - - me._drawStart = start; - me._drawCount = count; - - if (scaleRangesChanged(meta) && !animationsDisabled) { - start = 0; - count = points.length; - } - - // Update Line - line.points = points; - - // In resize mode only point locations change, so no need to set the options. - if (mode !== 'resize') { - me.updateElement(line, undefined, { - animated: !animationsDisabled, - options: me.resolveDatasetElementOptions() - }, mode); - } - - // Update Points - me.updateElements(points, start, count, mode); - } - - updateElements(points, start, count, mode) { - const me = this; - const reset = mode === 'reset'; - const {xScale, yScale, _stacked} = me._cachedMeta; - const firstOpts = me.resolveDataElementOptions(start, mode); - const sharedOptions = me.getSharedOptions(firstOpts); - const includeOptions = me.includeOptions(mode, sharedOptions); - const spanGaps = valueOrDefault(me._config.spanGaps, me.chart.options.spanGaps); - const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY; - const directUpdate = me.chart._animationsDisabled || reset || mode === 'none'; - let prevParsed = start > 0 && me.getParsed(start - 1); - - for (let i = start; i < start + count; ++i) { - const point = points[i]; - const parsed = me.getParsed(i); - const properties = directUpdate ? point : {}; - const x = properties.x = xScale.getPixelForValue(parsed.x, i); - const y = properties.y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed) : parsed.y, i); - properties.skip = isNaN(x) || isNaN(y); - properties.stop = i > 0 && (parsed.x - prevParsed.x) > maxGapLength; - - if (includeOptions) { - properties.options = sharedOptions || me.resolveDataElementOptions(i, mode); - } - - if (!directUpdate) { - me.updateElement(point, i, properties, mode); - } - - prevParsed = parsed; - } - - me.updateSharedOptions(sharedOptions, mode, firstOpts); - } - - /** + initialize() { + this.enableOptionSharing = true; + super.initialize(); + } + + update(mode) { + const me = this; + const meta = me._cachedMeta; + const {dataset: line, data: points = []} = meta; + // @ts-ignore + const animationsDisabled = me.chart._animationsDisabled; + let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); + + me._drawStart = start; + me._drawCount = count; + + if (scaleRangesChanged(meta) && !animationsDisabled) { + start = 0; + count = points.length; + } + + // Update Line + line.points = points; + + // In resize mode only point locations change, so no need to set the options. + if (mode !== 'resize') { + me.updateElement(line, undefined, { + animated: !animationsDisabled, + options: me.resolveDatasetElementOptions() + }, mode); + } + + // Update Points + me.updateElements(points, start, count, mode); + } + + updateElements(points, start, count, mode) { + const me = this; + const reset = mode === 'reset'; + const {xScale, yScale, _stacked} = me._cachedMeta; + const firstOpts = me.resolveDataElementOptions(start, mode); + const sharedOptions = me.getSharedOptions(firstOpts); + const includeOptions = me.includeOptions(mode, sharedOptions); + const spanGaps = valueOrDefault(me._config.spanGaps, me.chart.options.spanGaps); + const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY; + const directUpdate = me.chart._animationsDisabled || reset || mode === 'none'; + let prevParsed = start > 0 && me.getParsed(start - 1); + + for (let i = start; i < start + count; ++i) { + const point = points[i]; + const parsed = me.getParsed(i); + const properties = directUpdate ? point : {}; + const x = properties.x = xScale.getPixelForValue(parsed.x, i); + const y = properties.y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed) : parsed.y, i); + properties.skip = isNaN(x) || isNaN(y); + properties.stop = i > 0 && (parsed.x - prevParsed.x) > maxGapLength; + + if (includeOptions) { + properties.options = sharedOptions || me.resolveDataElementOptions(i, mode); + } + + if (!directUpdate) { + me.updateElement(point, i, properties, mode); + } + + prevParsed = parsed; + } + + me.updateSharedOptions(sharedOptions, mode, firstOpts); + } + + /** * @param {boolean} [active] * @protected */ - resolveDatasetElementOptions(active) { - const me = this; - const config = me._config; - const options = me.chart.options; - const lineOptions = options.elements.line; - const values = super.resolveDatasetElementOptions(active); - const showLine = valueOrDefault(config.showLine, options.showLine); - - // 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 - values.spanGaps = valueOrDefault(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault(config.tension, lineOptions.tension); - values.stepped = resolve([config.stepped, lineOptions.stepped]); - - if (!showLine) { - values.borderWidth = 0; - } - - return values; - } - - /** + resolveDatasetElementOptions(active) { + const me = this; + const config = me._config; + const options = me.chart.options; + const lineOptions = options.elements.line; + const values = super.resolveDatasetElementOptions(active); + const showLine = valueOrDefault(config.showLine, options.showLine); + + // 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 + values.spanGaps = valueOrDefault(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault(config.tension, lineOptions.tension); + values.stepped = resolve([config.stepped, lineOptions.stepped]); + + if (!showLine) { + values.borderWidth = 0; + } + + return values; + } + + /** * @protected */ - getMaxOverflow() { - const me = this; - const meta = me._cachedMeta; - const border = meta.dataset.options.borderWidth || 0; - const data = meta.data || []; - if (!data.length) { - return border; - } - const firstPoint = data[0].size(); - const lastPoint = data[data.length - 1].size(); - return Math.max(border, firstPoint, lastPoint) / 2; - } - - draw() { - this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea); - super.draw(); - } + getMaxOverflow() { + const me = this; + const meta = me._cachedMeta; + const border = meta.dataset.options.borderWidth || 0; + const data = meta.data || []; + if (!data.length) { + return border; + } + const firstPoint = data[0].size(); + const lastPoint = data[data.length - 1].size(); + return Math.max(border, firstPoint, lastPoint) / 2; + } + + draw() { + this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea); + super.draw(); + } } LineController.id = 'line'; @@ -131,101 +131,101 @@ LineController.id = 'line'; * @type {any} */ LineController.defaults = { - datasetElementType: 'line', - datasetElementOptions: [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth', - 'capBezierPoints', - 'cubicInterpolationMode', - 'fill' - ], - - dataElementType: 'point', - dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverHitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - showLine: true, - spanGaps: false, - - interaction: { - mode: 'index' - }, - - hover: {}, - - scales: { - _index_: { - type: 'category', - }, - _value_: { - type: 'linear', - }, - } + datasetElementType: 'line', + datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'capBezierPoints', + 'cubicInterpolationMode', + 'fill' + ], + + dataElementType: 'point', + dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverHitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + showLine: true, + spanGaps: false, + + interaction: { + mode: 'index' + }, + + hover: {}, + + scales: { + _index_: { + type: 'category', + }, + _value_: { + type: 'linear', + }, + } }; function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) { - const pointCount = points.length; - - let start = 0; - let count = pointCount; - - if (meta._sorted) { - const {iScale, _parsed} = meta; - const axis = iScale.axis; - const {min, max, minDefined, maxDefined} = iScale.getUserBounds(); - if (minDefined) { - start = _limitValue(Math.min( - _lookupByKey(_parsed, iScale.axis, min).lo, - animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), - 0, pointCount - 1); - } - if (maxDefined) { - count = _limitValue(Math.max( - _lookupByKey(_parsed, iScale.axis, max).hi + 1, - animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1), - start, pointCount) - start; - } else { - count = pointCount - start; - } - } - - return {start, count}; + const pointCount = points.length; + + let start = 0; + let count = pointCount; + + if (meta._sorted) { + const {iScale, _parsed} = meta; + const axis = iScale.axis; + const {min, max, minDefined, maxDefined} = iScale.getUserBounds(); + if (minDefined) { + start = _limitValue(Math.min( + _lookupByKey(_parsed, iScale.axis, min).lo, + animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), + 0, pointCount - 1); + } + if (maxDefined) { + count = _limitValue(Math.max( + _lookupByKey(_parsed, iScale.axis, max).hi + 1, + animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1), + start, pointCount) - start; + } else { + count = pointCount - start; + } + } + + return {start, count}; } function scaleRangesChanged(meta) { - const {xScale, yScale, _scaleRanges} = meta; - const newRanges = { - xmin: xScale.min, - xmax: xScale.max, - ymin: yScale.min, - ymax: yScale.max - }; - if (!_scaleRanges) { - meta._scaleRanges = newRanges; - return true; - } - const changed = _scaleRanges.xmin !== xScale.min + const {xScale, yScale, _scaleRanges} = meta; + const newRanges = { + xmin: xScale.min, + xmax: xScale.max, + ymin: yScale.min, + ymax: yScale.max + }; + if (!_scaleRanges) { + meta._scaleRanges = newRanges; + return true; + } + const changed = _scaleRanges.xmin !== xScale.min || _scaleRanges.xmax !== xScale.max || _scaleRanges.ymin !== yScale.min || _scaleRanges.ymax !== yScale.max; - Object.assign(_scaleRanges, newRanges); - return changed; + Object.assign(_scaleRanges, newRanges); + return changed; } diff --git a/src/controllers/controller.pie.js b/src/controllers/controller.pie.js index de95a69ef5d..158590a18f0 100644 --- a/src/controllers/controller.pie.js +++ b/src/controllers/controller.pie.js @@ -11,5 +11,5 @@ PieController.id = 'pie'; * @type {any} */ PieController.defaults = { - cutoutPercentage: 0 + cutoutPercentage: 0 }; diff --git a/src/controllers/controller.polarArea.js b/src/controllers/controller.polarArea.js index 432268a240e..d322e70a3e6 100644 --- a/src/controllers/controller.polarArea.js +++ b/src/controllers/controller.polarArea.js @@ -2,130 +2,130 @@ import DatasetController from '../core/core.datasetController'; import {resolve, toRadians, PI} from '../helpers/index'; function getStartAngleRadians(deg) { - // radialLinear scale draws angleLines using startAngle. 0 is expected to be at top. - // Here we adjust to standard unit circle used in drawing, where 0 is at right. - return toRadians(deg) - 0.5 * PI; + // radialLinear scale draws angleLines using startAngle. 0 is expected to be at top. + // Here we adjust to standard unit circle used in drawing, where 0 is at right. + return toRadians(deg) - 0.5 * PI; } export default class PolarAreaController extends DatasetController { - constructor(chart, datasetIndex) { - super(chart, datasetIndex); + constructor(chart, datasetIndex) { + super(chart, datasetIndex); - this.innerRadius = undefined; - this.outerRadius = undefined; - } + this.innerRadius = undefined; + this.outerRadius = undefined; + } - update(mode) { - const arcs = this._cachedMeta.data; + update(mode) { + const arcs = this._cachedMeta.data; - this._updateRadius(); - this.updateElements(arcs, 0, arcs.length, mode); - } + this._updateRadius(); + this.updateElements(arcs, 0, arcs.length, mode); + } - /** + /** * @private */ - _updateRadius() { - const me = this; - const chart = me.chart; - const chartArea = chart.chartArea; - const opts = chart.options; - const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); - - const outerRadius = Math.max(minSize / 2, 0); - const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); - const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount(); - - me.outerRadius = outerRadius - (radiusLength * me.index); - me.innerRadius = me.outerRadius - radiusLength; - } - - updateElements(arcs, start, count, mode) { - const me = this; - const reset = mode === 'reset'; - const chart = me.chart; - const dataset = me.getDataset(); - const opts = chart.options; - const animationOpts = opts.animation; - const scale = me._cachedMeta.rScale; - const centerX = scale.xCenter; - const centerY = scale.yCenter; - const datasetStartAngle = getStartAngleRadians(opts.startAngle); - let angle = datasetStartAngle; - let i; - - me._cachedMeta.count = me.countVisibleElements(); - - for (i = 0; i < start; ++i) { - angle += me._computeAngle(i, mode); - } - for (i = start; i < start + count; i++) { - const arc = arcs[i]; - let startAngle = angle; - let endAngle = angle + me._computeAngle(i, mode); - let outerRadius = this.chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0; - angle = endAngle; - - if (reset) { - if (animationOpts.animateScale) { - outerRadius = 0; - } - if (animationOpts.animateRotate) { - startAngle = datasetStartAngle; - endAngle = datasetStartAngle; - } - } - - const properties = { - x: centerX, - y: centerY, - innerRadius: 0, - outerRadius, - startAngle, - endAngle, - options: me.resolveDataElementOptions(i, mode) - }; - - me.updateElement(arc, i, properties, mode); - } - } - - countVisibleElements() { - const dataset = this.getDataset(); - const meta = this._cachedMeta; - let count = 0; - - meta.data.forEach((element, index) => { - if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) { - count++; - } - }); - - return count; - } - - /** + _updateRadius() { + const me = this; + const chart = me.chart; + const chartArea = chart.chartArea; + const opts = chart.options; + const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + const outerRadius = Math.max(minSize / 2, 0); + const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = outerRadius - (radiusLength * me.index); + me.innerRadius = me.outerRadius - radiusLength; + } + + updateElements(arcs, start, count, mode) { + const me = this; + const reset = mode === 'reset'; + const chart = me.chart; + const dataset = me.getDataset(); + const opts = chart.options; + const animationOpts = opts.animation; + const scale = me._cachedMeta.rScale; + const centerX = scale.xCenter; + const centerY = scale.yCenter; + const datasetStartAngle = getStartAngleRadians(opts.startAngle); + let angle = datasetStartAngle; + let i; + + me._cachedMeta.count = me.countVisibleElements(); + + for (i = 0; i < start; ++i) { + angle += me._computeAngle(i, mode); + } + for (i = start; i < start + count; i++) { + const arc = arcs[i]; + let startAngle = angle; + let endAngle = angle + me._computeAngle(i, mode); + let outerRadius = this.chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0; + angle = endAngle; + + if (reset) { + if (animationOpts.animateScale) { + outerRadius = 0; + } + if (animationOpts.animateRotate) { + startAngle = datasetStartAngle; + endAngle = datasetStartAngle; + } + } + + const properties = { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius, + startAngle, + endAngle, + options: me.resolveDataElementOptions(i, mode) + }; + + me.updateElement(arc, i, properties, mode); + } + } + + countVisibleElements() { + const dataset = this.getDataset(); + const meta = this._cachedMeta; + let count = 0; + + meta.data.forEach((element, index) => { + if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) { + count++; + } + }); + + return count; + } + + /** * @private */ - _computeAngle(index, mode) { - const me = this; - const meta = me._cachedMeta; - const count = meta.count; - const dataset = me.getDataset(); - - if (isNaN(dataset.data[index]) || !this.chart.getDataVisibility(index)) { - return 0; - } - - // Scriptable options - const context = me.getContext(index, mode === 'active'); - - return toRadians(resolve([ - me.chart.options.elements.arc.angle, - 360 / count - ], context, index)); - } + _computeAngle(index, mode) { + const me = this; + const meta = me._cachedMeta; + const count = meta.count; + const dataset = me.getDataset(); + + if (isNaN(dataset.data[index]) || !this.chart.getDataVisibility(index)) { + return 0; + } + + // Scriptable options + const context = me.getContext(index, mode === 'active'); + + return toRadians(resolve([ + me.chart.options.elements.arc.angle, + 360 / count + ], context, index)); + } } PolarAreaController.id = 'polarArea'; @@ -134,87 +134,87 @@ PolarAreaController.id = 'polarArea'; * @type {any} */ PolarAreaController.defaults = { - dataElementType: 'arc', - dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'offset' - ], - - animation: { - numbers: { - type: 'number', - properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius'] - }, - animateRotate: true, - animateScale: true - }, - aspectRatio: 1, - datasets: { - indexAxis: 'r' - }, - scales: { - r: { - type: 'radialLinear', - angleLines: { - display: false - }, - beginAtZero: true, - gridLines: { - circular: true - }, - pointLabels: { - display: false - } - } - }, - - startAngle: 0, - plugins: { - legend: { - labels: { - generateLabels(chart) { - const data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map((label, i) => { - const meta = chart.getDatasetMeta(0); - const style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: !chart.getDataVisibility(i), - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick(e, legendItem, legend) { - legend.chart.toggleDataVisibility(legendItem.index); - legend.chart.update(); - } - }, - - // Need to override these to give a nice default - tooltip: { - callbacks: { - title() { - return ''; - }, - label(context) { - return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue; - } - } - } - } + dataElementType: 'arc', + dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'offset' + ], + + animation: { + numbers: { + type: 'number', + properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius'] + }, + animateRotate: true, + animateScale: true + }, + aspectRatio: 1, + datasets: { + indexAxis: 'r' + }, + scales: { + r: { + type: 'radialLinear', + angleLines: { + display: false + }, + beginAtZero: true, + gridLines: { + circular: true + }, + pointLabels: { + display: false + } + } + }, + + startAngle: 0, + plugins: { + legend: { + labels: { + generateLabels(chart) { + const data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map((label, i) => { + const meta = chart.getDatasetMeta(0); + const style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: !chart.getDataVisibility(i), + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick(e, legendItem, legend) { + legend.chart.toggleDataVisibility(legendItem.index); + legend.chart.update(); + } + }, + + // Need to override these to give a nice default + tooltip: { + callbacks: { + title() { + return ''; + }, + label(context) { + return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue; + } + } + } + } }; diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index 74f03fb9286..e197f328278 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -3,90 +3,90 @@ import {valueOrDefault} from '../helpers/helpers.core'; export default class RadarController extends DatasetController { - /** + /** * @protected */ - getLabelAndValue(index) { - const me = this; - const vScale = me._cachedMeta.vScale; - const parsed = me.getParsed(index); - - return { - label: vScale.getLabels()[index], - value: '' + vScale.getLabelForValue(parsed[vScale.axis]) - }; - } - - update(mode) { - const me = this; - const meta = me._cachedMeta; - const line = meta.dataset; - const points = meta.data || []; - const labels = meta.iScale.getLabels(); - - // Update Line - line.points = points; - // In resize mode only point locations change, so no need to set the points or options. - if (mode !== 'resize') { - const properties = { - _loop: true, - _fullLoop: labels.length === points.length, - options: me.resolveDatasetElementOptions() - }; - - me.updateElement(line, undefined, properties, mode); - } - - // Update Points - me.updateElements(points, 0, points.length, mode); - } - - updateElements(points, start, count, mode) { - const me = this; - const dataset = me.getDataset(); - const scale = me._cachedMeta.rScale; - const reset = mode === 'reset'; - - for (let i = start; i < start + count; i++) { - const point = points[i]; - const options = me.resolveDataElementOptions(i, mode); - const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]); - - const x = reset ? scale.xCenter : pointPosition.x; - const y = reset ? scale.yCenter : pointPosition.y; - - const properties = { - x, - y, - angle: pointPosition.angle, - skip: isNaN(x) || isNaN(y), - options - }; - - me.updateElement(point, i, properties, mode); - } - } - - /** + getLabelAndValue(index) { + const me = this; + const vScale = me._cachedMeta.vScale; + const parsed = me.getParsed(index); + + return { + label: vScale.getLabels()[index], + value: '' + vScale.getLabelForValue(parsed[vScale.axis]) + }; + } + + update(mode) { + const me = this; + const meta = me._cachedMeta; + const line = meta.dataset; + const points = meta.data || []; + const labels = meta.iScale.getLabels(); + + // Update Line + line.points = points; + // In resize mode only point locations change, so no need to set the points or options. + if (mode !== 'resize') { + const properties = { + _loop: true, + _fullLoop: labels.length === points.length, + options: me.resolveDatasetElementOptions() + }; + + me.updateElement(line, undefined, properties, mode); + } + + // Update Points + me.updateElements(points, 0, points.length, mode); + } + + updateElements(points, start, count, mode) { + const me = this; + const dataset = me.getDataset(); + const scale = me._cachedMeta.rScale; + const reset = mode === 'reset'; + + for (let i = start; i < start + count; i++) { + const point = points[i]; + const options = me.resolveDataElementOptions(i, mode); + const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]); + + const x = reset ? scale.xCenter : pointPosition.x; + const y = reset ? scale.yCenter : pointPosition.y; + + const properties = { + x, + y, + angle: pointPosition.angle, + skip: isNaN(x) || isNaN(y), + options + }; + + me.updateElement(point, i, properties, mode); + } + } + + /** * @param {boolean} [active] * @protected */ - resolveDatasetElementOptions(active) { - const me = this; - const config = me._config; - const options = me.chart.options; - const values = super.resolveDatasetElementOptions(active); - const showLine = valueOrDefault(config.showLine, options.showLine); - - values.spanGaps = valueOrDefault(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault(config.tension, options.elements.line.tension); - - if (!showLine) { - values.borderWidth = 0; - } - - return values; - } + resolveDatasetElementOptions(active) { + const me = this; + const config = me._config; + const options = me.chart.options; + const values = super.resolveDatasetElementOptions(active); + const showLine = valueOrDefault(config.showLine, options.showLine); + + values.spanGaps = valueOrDefault(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault(config.tension, options.elements.line.tension); + + if (!showLine) { + values.borderWidth = 0; + } + + return values; + } } RadarController.id = 'radar'; @@ -95,47 +95,47 @@ RadarController.id = 'radar'; * @type {any} */ RadarController.defaults = { - datasetElementType: 'line', - datasetElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderCapStyle', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth', - 'fill' - ], - - dataElementType: 'point', - dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - aspectRatio: 1, - spanGaps: false, - scales: { - r: { - type: 'radialLinear', - } - }, - datasets: { - indexAxis: 'r' - }, - elements: { - line: { - fill: 'start', - tension: 0 // no bezier in radar - } - } + datasetElementType: 'line', + datasetElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'fill' + ], + + dataElementType: 'point', + dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + aspectRatio: 1, + spanGaps: false, + scales: { + r: { + type: 'radialLinear', + } + }, + datasets: { + indexAxis: 'r' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } }; diff --git a/src/controllers/controller.scatter.js b/src/controllers/controller.scatter.js index e13bf50c1a4..cc9823f4585 100644 --- a/src/controllers/controller.scatter.js +++ b/src/controllers/controller.scatter.js @@ -10,34 +10,34 @@ ScatterController.id = 'scatter'; * @type {any} */ ScatterController.defaults = { - scales: { - x: { - type: 'linear' - }, - y: { - type: 'linear' - } - }, + scales: { + x: { + type: 'linear' + }, + y: { + type: 'linear' + } + }, - datasets: { - showLine: false, - fill: false - }, + datasets: { + showLine: false, + fill: false + }, - interaction: { - mode: 'point' - }, + interaction: { + mode: 'point' + }, - plugins: { - tooltip: { - callbacks: { - title() { - return ''; // doesn't make sense for scatter since data are formatted as a point - }, - label(item) { - return '(' + item.label + ', ' + item.formattedValue + ')'; - } - } - } - } + plugins: { + tooltip: { + callbacks: { + title() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label(item) { + return '(' + item.label + ', ' + item.formattedValue + ')'; + } + } + } + } }; diff --git a/src/core/core.adapters.js b/src/core/core.adapters.js index 5a13a8304b0..78fad868f08 100644 --- a/src/core/core.adapters.js +++ b/src/core/core.adapters.js @@ -8,7 +8,7 @@ * @return {*} */ function abstract() { - throw new Error('This method is not implemented: either no adapter can be found or an incomplete integration was provided.'); + throw new Error('This method is not implemented: either no adapter can be found or an incomplete integration was provided.'); } /** @@ -26,62 +26,62 @@ function abstract() { export class DateAdapter { - constructor(options) { - this.options = options || {}; - } + constructor(options) { + this.options = options || {}; + } - /** + /** * Returns a map of time formats for the supported formatting units defined * in Unit as well as 'datetime' representing a detailed date/time string. * @returns {{string: string}} */ - formats() { - return abstract(); - } + formats() { + return abstract(); + } - /** + /** * Parses the given `value` and return the associated timestamp. * @param {any} value - the value to parse (usually comes from the data) * @param {string} [format] - the expected data format * @returns {(number|null)} */ - parse(value, format) { // eslint-disable-line no-unused-vars - return abstract(); - } + parse(value, format) { // eslint-disable-line no-unused-vars + return abstract(); + } - /** + /** * Returns the formatted date in the specified `format` for a given `timestamp`. * @param {number} timestamp - the timestamp to format * @param {string} format - the date/time token * @return {string} */ - format(timestamp, format) { // eslint-disable-line no-unused-vars - return abstract(); - } + format(timestamp, format) { // eslint-disable-line no-unused-vars + return abstract(); + } - /** + /** * Adds the specified `amount` of `unit` to the given `timestamp`. * @param {number} timestamp - the input timestamp * @param {number} amount - the amount to add * @param {Unit} unit - the unit as string * @return {number} */ - add(timestamp, amount, unit) { // eslint-disable-line no-unused-vars - return abstract(); - } + add(timestamp, amount, unit) { // eslint-disable-line no-unused-vars + return abstract(); + } - /** + /** * Returns the number of `unit` between the given timestamps. * @param {number} a - the input timestamp (reference) * @param {number} b - the timestamp to subtract * @param {Unit} unit - the unit as string * @return {number} */ - diff(a, b, unit) { // eslint-disable-line no-unused-vars - return abstract(); - } + diff(a, b, unit) { // eslint-disable-line no-unused-vars + return abstract(); + } - /** + /** * Returns start of `unit` for the given `timestamp`. * @param {number} timestamp - the input timestamp * @param {Unit|'isoWeek'} unit - the unit as string @@ -89,26 +89,26 @@ export class DateAdapter { * and 7 being Sunday (only needed if param *unit* is `isoWeek`). * @return {number} */ - startOf(timestamp, unit, weekday) { // eslint-disable-line no-unused-vars - return abstract(); - } + startOf(timestamp, unit, weekday) { // eslint-disable-line no-unused-vars + return abstract(); + } - /** + /** * Returns end of `unit` for the given `timestamp`. * @param {number} timestamp - the input timestamp * @param {Unit|'isoWeek'} unit - the unit as string * @return {number} */ - endOf(timestamp, unit) { // eslint-disable-line no-unused-vars - return abstract(); - } + endOf(timestamp, unit) { // eslint-disable-line no-unused-vars + return abstract(); + } } DateAdapter.override = function(members) { - Object.assign(DateAdapter.prototype, members); + Object.assign(DateAdapter.prototype, members); }; export default { - _date: DateAdapter + _date: DateAdapter }; diff --git a/src/core/core.animation.js b/src/core/core.animation.js index 100784e59cc..7be74bcef5d 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -4,113 +4,113 @@ import {color as helpersColor} from '../helpers/helpers.color'; const transparent = 'transparent'; const interpolators = { - boolean(from, to, factor) { - return factor > 0.5 ? to : from; - }, - color(from, to, factor) { - const c0 = helpersColor(from || transparent); - const c1 = c0.valid && helpersColor(to || transparent); - return c1 && c1.valid - ? c1.mix(c0, factor).hexString() - : to; - }, - number(from, to, factor) { - return from + (to - from) * factor; - } + boolean(from, to, factor) { + return factor > 0.5 ? to : from; + }, + color(from, to, factor) { + const c0 = helpersColor(from || transparent); + const c1 = c0.valid && helpersColor(to || transparent); + return c1 && c1.valid + ? c1.mix(c0, factor).hexString() + : to; + }, + number(from, to, factor) { + return from + (to - from) * factor; + } }; export default class Animation { - constructor(cfg, target, prop, to) { - const currentValue = target[prop]; - - to = resolve([cfg.to, to, currentValue, cfg.from]); - const from = resolve([cfg.from, currentValue, to]); - - this._active = true; - this._fn = cfg.fn || interpolators[cfg.type || typeof from]; - this._easing = effects[cfg.easing || 'linear']; - this._start = Math.floor(Date.now() + (cfg.delay || 0)); - this._duration = Math.floor(cfg.duration); - this._loop = !!cfg.loop; - this._target = target; - this._prop = prop; - this._from = from; - this._to = to; - this._promises = undefined; - } - - active() { - return this._active; - } - - update(cfg, to, date) { - const me = this; - if (me._active) { - me._notify(false); - - const currentValue = me._target[me._prop]; - const elapsed = date - me._start; - const remain = me._duration - elapsed; - me._start = date; - me._duration = Math.floor(Math.max(remain, cfg.duration)); - me._loop = !!cfg.loop; - me._to = resolve([cfg.to, to, currentValue, cfg.from]); - me._from = resolve([cfg.from, currentValue, to]); - } - } - - cancel() { - const me = this; - if (me._active) { - // update current evaluated value, for smoother animations - me.tick(Date.now()); - me._active = false; - me._notify(false); - } - } - - tick(date) { - const me = this; - const elapsed = date - me._start; - const duration = me._duration; - const prop = me._prop; - const from = me._from; - const loop = me._loop; - const to = me._to; - let factor; - - me._active = from !== to && (loop || (elapsed < duration)); - - if (!me._active) { - me._target[prop] = to; - me._notify(true); - return; - } - - if (elapsed < 0) { - me._target[prop] = from; - return; - } - - factor = (elapsed / duration) % 2; - factor = loop && factor > 1 ? 2 - factor : factor; - factor = me._easing(Math.min(1, Math.max(0, factor))); - - me._target[prop] = me._fn(from, to, factor); - } - - wait() { - const promises = this._promises || (this._promises = []); - return new Promise((res, rej) => { - promises.push({res, rej}); - }); - } - - _notify(resolved) { - const method = resolved ? 'res' : 'rej'; - const promises = this._promises || []; - for (let i = 0; i < promises.length; i++) { - promises[i][method](); - } - } + constructor(cfg, target, prop, to) { + const currentValue = target[prop]; + + to = resolve([cfg.to, to, currentValue, cfg.from]); + const from = resolve([cfg.from, currentValue, to]); + + this._active = true; + this._fn = cfg.fn || interpolators[cfg.type || typeof from]; + this._easing = effects[cfg.easing || 'linear']; + this._start = Math.floor(Date.now() + (cfg.delay || 0)); + this._duration = Math.floor(cfg.duration); + this._loop = !!cfg.loop; + this._target = target; + this._prop = prop; + this._from = from; + this._to = to; + this._promises = undefined; + } + + active() { + return this._active; + } + + update(cfg, to, date) { + const me = this; + if (me._active) { + me._notify(false); + + const currentValue = me._target[me._prop]; + const elapsed = date - me._start; + const remain = me._duration - elapsed; + me._start = date; + me._duration = Math.floor(Math.max(remain, cfg.duration)); + me._loop = !!cfg.loop; + me._to = resolve([cfg.to, to, currentValue, cfg.from]); + me._from = resolve([cfg.from, currentValue, to]); + } + } + + cancel() { + const me = this; + if (me._active) { + // update current evaluated value, for smoother animations + me.tick(Date.now()); + me._active = false; + me._notify(false); + } + } + + tick(date) { + const me = this; + const elapsed = date - me._start; + const duration = me._duration; + const prop = me._prop; + const from = me._from; + const loop = me._loop; + const to = me._to; + let factor; + + me._active = from !== to && (loop || (elapsed < duration)); + + if (!me._active) { + me._target[prop] = to; + me._notify(true); + return; + } + + if (elapsed < 0) { + me._target[prop] = from; + return; + } + + factor = (elapsed / duration) % 2; + factor = loop && factor > 1 ? 2 - factor : factor; + factor = me._easing(Math.min(1, Math.max(0, factor))); + + me._target[prop] = me._fn(from, to, factor); + } + + wait() { + const promises = this._promises || (this._promises = []); + return new Promise((res, rej) => { + promises.push({res, rej}); + }); + } + + _notify(resolved) { + const method = resolved ? 'res' : 'rej'; + const promises = this._promises || []; + for (let i = 0; i < promises.length; i++) { + promises[i][method](); + } + } } diff --git a/src/core/core.animations.js b/src/core/core.animations.js index 94d28595033..285592db105 100644 --- a/src/core/core.animations.js +++ b/src/core/core.animations.js @@ -7,237 +7,237 @@ const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension']; const colors = ['borderColor', 'backgroundColor']; defaults.set('animation', { - // Plain properties can be overridden in each object - duration: 1000, - easing: 'easeOutQuart', - onProgress: noop, - onComplete: noop, - - // Property sets - colors: { - type: 'color', - properties: colors - }, - numbers: { - type: 'number', - properties: numbers - }, - - // Update modes. These are overrides / additions to the above animations. - active: { - duration: 400 - }, - resize: { - duration: 0 - }, - show: { - colors: { - type: 'color', - properties: colors, - from: 'transparent' - }, - visible: { - type: 'boolean', - duration: 0 // show immediately - }, - }, - hide: { - colors: { - type: 'color', - properties: colors, - to: 'transparent' - }, - visible: { - type: 'boolean', - easing: 'easeInExpo' // for keeping the dataset visible almost all the way through the animation - }, - } + // Plain properties can be overridden in each object + duration: 1000, + easing: 'easeOutQuart', + onProgress: noop, + onComplete: noop, + + // Property sets + colors: { + type: 'color', + properties: colors + }, + numbers: { + type: 'number', + properties: numbers + }, + + // Update modes. These are overrides / additions to the above animations. + active: { + duration: 400 + }, + resize: { + duration: 0 + }, + show: { + colors: { + type: 'color', + properties: colors, + from: 'transparent' + }, + visible: { + type: 'boolean', + duration: 0 // show immediately + }, + }, + hide: { + colors: { + type: 'color', + properties: colors, + to: 'transparent' + }, + visible: { + type: 'boolean', + easing: 'easeInExpo' // for keeping the dataset visible almost all the way through the animation + }, + } }); function copyOptions(target, values) { - const oldOpts = target.options; - const newOpts = values.options; - if (!oldOpts || !newOpts) { - return; - } - if (oldOpts.$shared && !newOpts.$shared) { - target.options = Object.assign({}, oldOpts, newOpts, {$shared: false}); - } else { - Object.assign(oldOpts, newOpts); - } - delete values.options; + const oldOpts = target.options; + const newOpts = values.options; + if (!oldOpts || !newOpts) { + return; + } + if (oldOpts.$shared && !newOpts.$shared) { + target.options = Object.assign({}, oldOpts, newOpts, {$shared: false}); + } else { + Object.assign(oldOpts, newOpts); + } + delete values.options; } function extensibleConfig(animations) { - const result = {}; - Object.keys(animations).forEach(key => { - const value = animations[key]; - if (!isObject(value)) { - result[key] = value; - } - }); - return result; + const result = {}; + Object.keys(animations).forEach(key => { + const value = animations[key]; + if (!isObject(value)) { + result[key] = value; + } + }); + return result; } export default class Animations { - constructor(chart, animations) { - this._chart = chart; - this._properties = new Map(); - this.configure(animations); - } - - configure(animations) { - if (!isObject(animations)) { - return; - } - - const animatedProps = this._properties; - const animDefaults = extensibleConfig(animations); - - Object.keys(animations).forEach(key => { - const cfg = animations[key]; - if (!isObject(cfg)) { - return; - } - (cfg.properties || [key]).forEach((prop) => { - // Can have only one config per animation. - if (!animatedProps.has(prop)) { - animatedProps.set(prop, Object.assign({}, animDefaults, cfg)); - } else if (prop === key) { - // Single property targetting config wins over multi-targetting. - // eslint-disable-next-line no-unused-vars - const {properties, ...inherited} = animatedProps.get(prop); - animatedProps.set(prop, Object.assign({}, inherited, cfg)); - } - }); - }); - } - - /** + constructor(chart, animations) { + this._chart = chart; + this._properties = new Map(); + this.configure(animations); + } + + configure(animations) { + if (!isObject(animations)) { + return; + } + + const animatedProps = this._properties; + const animDefaults = extensibleConfig(animations); + + Object.keys(animations).forEach(key => { + const cfg = animations[key]; + if (!isObject(cfg)) { + return; + } + (cfg.properties || [key]).forEach((prop) => { + // Can have only one config per animation. + if (!animatedProps.has(prop)) { + animatedProps.set(prop, Object.assign({}, animDefaults, cfg)); + } else if (prop === key) { + // Single property targetting config wins over multi-targetting. + // eslint-disable-next-line no-unused-vars + const {properties, ...inherited} = animatedProps.get(prop); + animatedProps.set(prop, Object.assign({}, inherited, cfg)); + } + }); + }); + } + + /** * Utility to handle animation of `options`. * @private */ - _animateOptions(target, values) { - const newOptions = values.options; - const options = resolveTargetOptions(target, newOptions); - if (!options) { - return []; - } - - const animations = this._createAnimations(options, newOptions); - if (newOptions.$shared && !options.$shared) { - // Going from distinct options to shared options: - // After all animations are done, assign the shared options object to the element - // So any new updates to the shared options are observed - awaitAll(target.options.$animations, newOptions).then(() => { - target.options = newOptions; - }, () => { - // rejected, noop - }); - } - - return animations; - } - - /** + _animateOptions(target, values) { + const newOptions = values.options; + const options = resolveTargetOptions(target, newOptions); + if (!options) { + return []; + } + + const animations = this._createAnimations(options, newOptions); + if (newOptions.$shared && !options.$shared) { + // Going from distinct options to shared options: + // After all animations are done, assign the shared options object to the element + // So any new updates to the shared options are observed + awaitAll(target.options.$animations, newOptions).then(() => { + target.options = newOptions; + }, () => { + // rejected, noop + }); + } + + return animations; + } + + /** * @private */ - _createAnimations(target, values) { - const animatedProps = this._properties; - const animations = []; - const running = target.$animations || (target.$animations = {}); - const props = Object.keys(values); - const date = Date.now(); - let i; - - for (i = props.length - 1; i >= 0; --i) { - const prop = props[i]; - if (prop.charAt(0) === '$') { - continue; - } - - if (prop === 'options') { - animations.push(...this._animateOptions(target, values)); - continue; - } - const value = values[prop]; - let animation = running[prop]; - const cfg = animatedProps.get(prop); - - if (animation) { - if (cfg && animation.active()) { - // There is an existing active animation, let's update that - animation.update(cfg, value, date); - continue; - } else { - animation.cancel(); - } - } - if (!cfg || !cfg.duration) { - // not animated, set directly to new value - target[prop] = value; - continue; - } - - running[prop] = animation = new Animation(cfg, target, prop, value); - animations.push(animation); - } - return animations; - } - - - /** + _createAnimations(target, values) { + const animatedProps = this._properties; + const animations = []; + const running = target.$animations || (target.$animations = {}); + const props = Object.keys(values); + const date = Date.now(); + let i; + + for (i = props.length - 1; i >= 0; --i) { + const prop = props[i]; + if (prop.charAt(0) === '$') { + continue; + } + + if (prop === 'options') { + animations.push(...this._animateOptions(target, values)); + continue; + } + const value = values[prop]; + let animation = running[prop]; + const cfg = animatedProps.get(prop); + + if (animation) { + if (cfg && animation.active()) { + // There is an existing active animation, let's update that + animation.update(cfg, value, date); + continue; + } else { + animation.cancel(); + } + } + if (!cfg || !cfg.duration) { + // not animated, set directly to new value + target[prop] = value; + continue; + } + + running[prop] = animation = new Animation(cfg, target, prop, value); + animations.push(animation); + } + return animations; + } + + + /** * Update `target` properties to new values, using configured animations * @param {object} target - object to update * @param {object} values - new target properties * @returns {boolean|undefined} - `true` if animations were started **/ - update(target, values) { - if (this._properties.size === 0) { - // Nothing is animated, just apply the new values. - // Options can be shared, need to account for that. - copyOptions(target, values); - // copyOptions removes the `options` from `values`, - // unless it can be directly assigned. - Object.assign(target, values); - return; - } - - const animations = this._createAnimations(target, values); - - if (animations.length) { - animator.add(this._chart, animations); - return true; - } - } + update(target, values) { + if (this._properties.size === 0) { + // Nothing is animated, just apply the new values. + // Options can be shared, need to account for that. + copyOptions(target, values); + // copyOptions removes the `options` from `values`, + // unless it can be directly assigned. + Object.assign(target, values); + return; + } + + const animations = this._createAnimations(target, values); + + if (animations.length) { + animator.add(this._chart, animations); + return true; + } + } } function awaitAll(animations, properties) { - const running = []; - const keys = Object.keys(properties); - for (let i = 0; i < keys.length; i++) { - const anim = animations[keys[i]]; - if (anim && anim.active()) { - running.push(anim.wait()); - } - } - // @ts-ignore - return Promise.all(running); + const running = []; + const keys = Object.keys(properties); + for (let i = 0; i < keys.length; i++) { + const anim = animations[keys[i]]; + if (anim && anim.active()) { + running.push(anim.wait()); + } + } + // @ts-ignore + return Promise.all(running); } function resolveTargetOptions(target, newOptions) { - if (!newOptions) { - return; - } - let options = target.options; - if (!options) { - target.options = newOptions; - return; - } - if (options.$shared && !newOptions.$shared) { - // Going from shared options to distinct one: - // Create new options object containing the old shared values and start updating that. - target.options = options = Object.assign({}, options, {$shared: false, $animations: {}}); - } - return options; + if (!newOptions) { + return; + } + let options = target.options; + if (!options) { + target.options = newOptions; + return; + } + if (options.$shared && !newOptions.$shared) { + // Going from shared options to distinct one: + // Create new options object containing the old shared values and start updating that. + target.options = options = Object.assign({}, options, {$shared: false, $animations: {}}); + } + return options; } diff --git a/src/core/core.animator.js b/src/core/core.animator.js index 64771ff5f24..1d78e34aca9 100644 --- a/src/core/core.animator.js +++ b/src/core/core.animator.js @@ -6,17 +6,17 @@ import {requestAnimFrame} from '../helpers/helpers.extras'; */ function drawFPS(chart, count, date, lastDate) { - const fps = (1000 / (date - lastDate)) | 0; - const ctx = chart.ctx; - ctx.save(); - ctx.clearRect(0, 0, 50, 24); - ctx.fillStyle = 'black'; - ctx.textAlign = 'right'; - if (count) { - ctx.fillText(count, 50, 8); - ctx.fillText(fps + ' fps', 50, 18); - } - ctx.restore(); + const fps = (1000 / (date - lastDate)) | 0; + const ctx = chart.ctx; + ctx.save(); + ctx.clearRect(0, 0, 50, 24); + ctx.fillStyle = 'black'; + ctx.textAlign = 'right'; + if (count) { + ctx.fillText(count, 50, 8); + ctx.fillText(fps + ' fps', 50, 18); + } + ctx.restore(); } /** @@ -24,204 +24,204 @@ function drawFPS(chart, count, date, lastDate) { * Note: class is export for typedoc */ export class Animator { - constructor() { - this._request = null; - this._charts = new Map(); - this._running = false; - this._lastDate = undefined; - } - - /** + constructor() { + this._request = null; + this._charts = new Map(); + this._running = false; + this._lastDate = undefined; + } + + /** * @private */ - _notify(chart, anims, date, type) { - const callbacks = anims.listeners[type] || []; - const numSteps = anims.duration; - - callbacks.forEach(fn => fn({ - chart, - numSteps, - currentStep: Math.min(date - anims.start, numSteps) - })); - } - - /** + _notify(chart, anims, date, type) { + const callbacks = anims.listeners[type] || []; + const numSteps = anims.duration; + + callbacks.forEach(fn => fn({ + chart, + numSteps, + currentStep: Math.min(date - anims.start, numSteps) + })); + } + + /** * @private */ - _refresh() { - const me = this; + _refresh() { + const me = this; - if (me._request) { - return; - } - me._running = true; + if (me._request) { + return; + } + me._running = true; - me._request = requestAnimFrame.call(window, () => { - me._update(); - me._request = null; + me._request = requestAnimFrame.call(window, () => { + me._update(); + me._request = null; - if (me._running) { - me._refresh(); - } - }); - } + if (me._running) { + me._refresh(); + } + }); + } - /** + /** * @private */ - _update() { - const me = this; - const date = Date.now(); - let remaining = 0; - - me._charts.forEach((anims, chart) => { - if (!anims.running || !anims.items.length) { - return; - } - const items = anims.items; - let i = items.length - 1; - let draw = false; - let item; - - for (; i >= 0; --i) { - item = items[i]; - - if (item._active) { - item.tick(date); - draw = true; - } else { - // Remove the item by replacing it with last item and removing the last - // A lot faster than splice. - items[i] = items[items.length - 1]; - items.pop(); - } - } - - if (draw) { - chart.draw(); - me._notify(chart, anims, date, 'progress'); - } - - if (chart.options.animation.debug) { - drawFPS(chart, items.length, date, me._lastDate); - } - - if (!items.length) { - anims.running = false; - me._notify(chart, anims, date, 'complete'); - } - - remaining += items.length; - }); - - me._lastDate = date; - - if (remaining === 0) { - me._running = false; - } - } - - /** + _update() { + const me = this; + const date = Date.now(); + let remaining = 0; + + me._charts.forEach((anims, chart) => { + if (!anims.running || !anims.items.length) { + return; + } + const items = anims.items; + let i = items.length - 1; + let draw = false; + let item; + + for (; i >= 0; --i) { + item = items[i]; + + if (item._active) { + item.tick(date); + draw = true; + } else { + // Remove the item by replacing it with last item and removing the last + // A lot faster than splice. + items[i] = items[items.length - 1]; + items.pop(); + } + } + + if (draw) { + chart.draw(); + me._notify(chart, anims, date, 'progress'); + } + + if (chart.options.animation.debug) { + drawFPS(chart, items.length, date, me._lastDate); + } + + if (!items.length) { + anims.running = false; + me._notify(chart, anims, date, 'complete'); + } + + remaining += items.length; + }); + + me._lastDate = date; + + if (remaining === 0) { + me._running = false; + } + } + + /** * @private */ - _getAnims(chart) { - const charts = this._charts; - let anims = charts.get(chart); - if (!anims) { - anims = { - running: false, - items: [], - listeners: { - complete: [], - progress: [] - } - }; - charts.set(chart, anims); - } - return anims; - } - - /** + _getAnims(chart) { + const charts = this._charts; + let anims = charts.get(chart); + if (!anims) { + anims = { + running: false, + items: [], + listeners: { + complete: [], + progress: [] + } + }; + charts.set(chart, anims); + } + return anims; + } + + /** * @param {Chart} chart * @param {string} event - event name * @param {Function} cb - callback */ - listen(chart, event, cb) { - this._getAnims(chart).listeners[event].push(cb); - } + listen(chart, event, cb) { + this._getAnims(chart).listeners[event].push(cb); + } - /** + /** * Add animations * @param {Chart} chart * @param {Animation[]} items - animations */ - add(chart, items) { - if (!items || !items.length) { - return; - } - this._getAnims(chart).items.push(...items); - } - - /** + add(chart, items) { + if (!items || !items.length) { + return; + } + this._getAnims(chart).items.push(...items); + } + + /** * Counts number of active animations for the chart * @param {Chart} chart */ - has(chart) { - return this._getAnims(chart).items.length > 0; - } + has(chart) { + return this._getAnims(chart).items.length > 0; + } - /** + /** * Start animating (all charts) * @param {Chart} chart */ - start(chart) { - const anims = this._charts.get(chart); - if (!anims) { - return; - } - anims.running = true; - anims.start = Date.now(); - anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0); - this._refresh(); - } - - running(chart) { - if (!this._running) { - return false; - } - const anims = this._charts.get(chart); - if (!anims || !anims.running || !anims.items.length) { - return false; - } - return true; - } - - /** + start(chart) { + const anims = this._charts.get(chart); + if (!anims) { + return; + } + anims.running = true; + anims.start = Date.now(); + anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0); + this._refresh(); + } + + running(chart) { + if (!this._running) { + return false; + } + const anims = this._charts.get(chart); + if (!anims || !anims.running || !anims.items.length) { + return false; + } + return true; + } + + /** * Stop all animations for the chart * @param {Chart} chart */ - stop(chart) { - const anims = this._charts.get(chart); - if (!anims || !anims.items.length) { - return; - } - const items = anims.items; - let i = items.length - 1; - - for (; i >= 0; --i) { - items[i].cancel(); - } - anims.items = []; - this._notify(chart, anims, Date.now(), 'complete'); - } - - /** + stop(chart) { + const anims = this._charts.get(chart); + if (!anims || !anims.items.length) { + return; + } + const items = anims.items; + let i = items.length - 1; + + for (; i >= 0; --i) { + items[i].cancel(); + } + anims.items = []; + this._notify(chart, anims, Date.now(), 'complete'); + } + + /** * Remove chart from Animator * @param {Chart} chart */ - remove(chart) { - return this._charts.delete(chart); - } + remove(chart) { + return this._charts.delete(chart); + } } // singleton instance diff --git a/src/core/core.config.js b/src/core/core.config.js index 6772182ff0f..06fcbbefff9 100644 --- a/src/core/core.config.js +++ b/src/core/core.config.js @@ -2,81 +2,81 @@ import defaults from './core.defaults'; import {mergeIf, merge, _merger} from '../helpers/helpers.core'; export function getIndexAxis(type, options) { - const typeDefaults = defaults.controllers[type] || {}; - const datasetDefaults = typeDefaults.datasets || {}; - const datasetOptions = options.datasets || {}; - const typeOptions = datasetOptions[type] || {}; - return typeOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x'; + const typeDefaults = defaults.controllers[type] || {}; + const datasetDefaults = typeDefaults.datasets || {}; + const datasetOptions = options.datasets || {}; + const typeOptions = datasetOptions[type] || {}; + return typeOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x'; } function getAxisFromDefaultScaleID(id, indexAxis) { - let axis = id; - if (id === '_index_') { - axis = indexAxis; - } else if (id === '_value_') { - axis = indexAxis === 'x' ? 'y' : 'x'; - } - return axis; + let axis = id; + if (id === '_index_') { + axis = indexAxis; + } else if (id === '_value_') { + axis = indexAxis === 'x' ? 'y' : 'x'; + } + return axis; } function getDefaultScaleIDFromAxis(axis, indexAxis) { - return axis === indexAxis ? '_index_' : '_value_'; + return axis === indexAxis ? '_index_' : '_value_'; } function axisFromPosition(position) { - if (position === 'top' || position === 'bottom') { - return 'x'; - } - if (position === 'left' || position === 'right') { - return 'y'; - } + if (position === 'top' || position === 'bottom') { + return 'x'; + } + if (position === 'left' || position === 'right') { + return 'y'; + } } export function determineAxis(id, scaleOptions) { - if (id === 'x' || id === 'y' || id === 'r') { - return id; - } - return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase(); + if (id === 'x' || id === 'y' || id === 'r') { + return id; + } + return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase(); } function mergeScaleConfig(config, options) { - const chartDefaults = defaults.controllers[config.type] || {scales: {}}; - const configScales = options.scales || {}; - const chartIndexAxis = getIndexAxis(config.type, options); - const firstIDs = Object.create(null); - const scales = Object.create(null); - - // First figure out first scale id's per axis. - Object.keys(configScales).forEach(id => { - const scaleConf = configScales[id]; - const axis = determineAxis(id, scaleConf); - const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); - const defaultScaleOptions = chartDefaults.scales || {}; - firstIDs[axis] = firstIDs[axis] || id; - scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]); - }); - - // Then merge dataset defaults to scale configs - config.data.datasets.forEach(dataset => { - const type = dataset.type || config.type; - const indexAxis = dataset.indexAxis || getIndexAxis(type, options); - const datasetDefaults = defaults.controllers[type] || {}; - const defaultScaleOptions = datasetDefaults.scales || {}; - Object.keys(defaultScaleOptions).forEach(defaultID => { - const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); - const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis; - scales[id] = scales[id] || Object.create(null); - mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]); - }); - }); - - // apply scale defaults, if not overridden by dataset defaults - Object.keys(scales).forEach(key => { - const scale = scales[key]; - mergeIf(scale, [defaults.scales[scale.type], defaults.scale]); - }); - - return scales; + const chartDefaults = defaults.controllers[config.type] || {scales: {}}; + const configScales = options.scales || {}; + const chartIndexAxis = getIndexAxis(config.type, options); + const firstIDs = Object.create(null); + const scales = Object.create(null); + + // First figure out first scale id's per axis. + Object.keys(configScales).forEach(id => { + const scaleConf = configScales[id]; + const axis = determineAxis(id, scaleConf); + const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); + const defaultScaleOptions = chartDefaults.scales || {}; + firstIDs[axis] = firstIDs[axis] || id; + scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]); + }); + + // Then merge dataset defaults to scale configs + config.data.datasets.forEach(dataset => { + const type = dataset.type || config.type; + const indexAxis = dataset.indexAxis || getIndexAxis(type, options); + const datasetDefaults = defaults.controllers[type] || {}; + const defaultScaleOptions = datasetDefaults.scales || {}; + Object.keys(defaultScaleOptions).forEach(defaultID => { + const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); + const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis; + scales[id] = scales[id] || Object.create(null); + mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]); + }); + }); + + // apply scale defaults, if not overridden by dataset defaults + Object.keys(scales).forEach(key => { + const scale = scales[key]; + mergeIf(scale, [defaults.scales[scale.type], defaults.scale]); + }); + + return scales; } /** @@ -85,101 +85,101 @@ function mergeScaleConfig(config, options) { * a deep copy of the result, thus doesn't alter inputs. */ function mergeConfig(...args/* config objects ... */) { - return merge(Object.create(null), args, { - merger(key, target, source, options) { - if (key !== 'scales' && key !== 'scale' && key !== 'controllers') { - _merger(key, target, source, options); - } - } - }); + return merge(Object.create(null), args, { + merger(key, target, source, options) { + if (key !== 'scales' && key !== 'scale' && key !== 'controllers') { + _merger(key, target, source, options); + } + } + }); } function includePluginDefaults(options) { - options.plugins = options.plugins || {}; - options.plugins.title = (options.plugins.title !== false) && merge(Object.create(null), [ - defaults.plugins.title, - options.plugins.title - ]); - - options.plugins.tooltip = (options.plugins.tooltip !== false) && merge(Object.create(null), [ - defaults.interaction, - defaults.plugins.tooltip, - options.interaction, - options.plugins.tooltip - ]); + options.plugins = options.plugins || {}; + options.plugins.title = (options.plugins.title !== false) && merge(Object.create(null), [ + defaults.plugins.title, + options.plugins.title + ]); + + options.plugins.tooltip = (options.plugins.tooltip !== false) && merge(Object.create(null), [ + defaults.interaction, + defaults.plugins.tooltip, + options.interaction, + options.plugins.tooltip + ]); } function includeDefaults(config, options) { - options = options || {}; + options = options || {}; - const scaleConfig = mergeScaleConfig(config, options); - const hoverEanbled = options.interaction !== false && options.hover !== false; + const scaleConfig = mergeScaleConfig(config, options); + const hoverEanbled = options.interaction !== false && options.hover !== false; - options = mergeConfig( - defaults, - defaults.controllers[config.type], - options); + options = mergeConfig( + defaults, + defaults.controllers[config.type], + options); - options.hover = hoverEanbled && merge(Object.create(null), [ - defaults.interaction, - defaults.hover, - options.interaction, - options.hover - ]); + options.hover = hoverEanbled && merge(Object.create(null), [ + defaults.interaction, + defaults.hover, + options.interaction, + options.hover + ]); - options.scales = scaleConfig; + options.scales = scaleConfig; - if (options.plugins !== false) { - includePluginDefaults(options); - } - return options; + if (options.plugins !== false) { + includePluginDefaults(options); + } + return options; } function initConfig(config) { - config = config || {}; + config = config || {}; - // Do NOT use mergeConfig for the data object because this method merges arrays - // and so would change references to labels and datasets, preventing data updates. - const data = config.data = config.data || {datasets: [], labels: []}; - data.datasets = data.datasets || []; - data.labels = data.labels || []; + // Do NOT use mergeConfig for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + const data = config.data = config.data || {datasets: [], labels: []}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; - config.options = includeDefaults(config, config.options); + config.options = includeDefaults(config, config.options); - return config; + return config; } export default class Config { - constructor(config) { - this._config = initConfig(config); - } - - get type() { - return this._config.type; - } - - set type(type) { - this._config.type = type; - } - - get data() { - return this._config.data; - } - - set data(data) { - this._config.data = data; - } - - get options() { - return this._config.options; - } - - get plugins() { - return this._config.plugins; - } - - update(options) { - const config = this._config; - config.options = includeDefaults(config, options); - } + constructor(config) { + this._config = initConfig(config); + } + + get type() { + return this._config.type; + } + + set type(type) { + this._config.type = type; + } + + get data() { + return this._config.data; + } + + set data(data) { + this._config.data = data; + } + + get options() { + return this._config.options; + } + + get plugins() { + return this._config.plugins; + } + + update(options) { + const config = this._config; + config.options = includeDefaults(config, options); + } } diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 8d257f9c318..cf0b75867e2 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -18,33 +18,33 @@ import {version} from '../../package.json'; const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea']; function positionIsHorizontal(position, axis) { - return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x'); + return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x'); } function compare2Level(l1, l2) { - return function(a, b) { - return a[l1] === b[l1] - ? a[l2] - b[l2] - : a[l1] - b[l1]; - }; + return function(a, b) { + return a[l1] === b[l1] + ? a[l2] - b[l2] + : a[l1] - b[l1]; + }; } function onAnimationsComplete(context) { - const chart = context.chart; - const animationOptions = chart.options.animation; + const chart = context.chart; + const animationOptions = chart.options.animation; - chart.notifyPlugins('afterRender'); - callCallback(animationOptions && animationOptions.onComplete, [context], chart); + chart.notifyPlugins('afterRender'); + callCallback(animationOptions && animationOptions.onComplete, [context], chart); } function onAnimationProgress(context) { - const chart = context.chart; - const animationOptions = chart.options.animation; - callCallback(animationOptions && animationOptions.onProgress, [context], chart); + const chart = context.chart; + const animationOptions = chart.options.animation; + callCallback(animationOptions && animationOptions.onProgress, [context], chart); } function isDomSupported() { - return typeof window !== 'undefined' && typeof document !== 'undefined'; + return typeof window !== 'undefined' && typeof document !== 'undefined'; } /** @@ -52,938 +52,938 @@ function isDomSupported() { * Attempt to unwrap the item passed into the chart constructor so that it is a canvas element (if possible). */ function getCanvas(item) { - if (isDomSupported() && typeof item === 'string') { - item = document.getElementById(item); - } else if (item && item.length) { - // Support for array based queries (such as jQuery) - item = item[0]; - } - - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - return item; + if (isDomSupported() && typeof item === 'string') { + item = document.getElementById(item); + } else if (item && item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + return item; } class Chart { - // eslint-disable-next-line max-statements - constructor(item, config) { - const me = this; + // eslint-disable-next-line max-statements + constructor(item, config) { + const me = this; - this.config = config = new Config(config); - const initialCanvas = getCanvas(item); - const existingChart = Chart.getChart(initialCanvas); - if (existingChart) { - throw new Error( - 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + + this.config = config = new Config(config); + const initialCanvas = getCanvas(item); + const existingChart = Chart.getChart(initialCanvas); + if (existingChart) { + throw new Error( + 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas can be reused.' - ); - } - - this.platform = me._initializePlatform(initialCanvas, config); - - const context = me.platform.acquireContext(initialCanvas, config); - const canvas = context && context.canvas; - const height = canvas && canvas.height; - const width = canvas && canvas.width; - - this.id = uid(); - this.ctx = context; - this.canvas = canvas; - this.width = width; - this.height = height; - this.aspectRatio = height ? width / height : null; - this.options = config.options; - this._layers = []; - this._metasets = []; - this.boxes = []; - this.currentDevicePixelRatio = undefined; - this.chartArea = undefined; - this._active = []; - this._lastEvent = undefined; - /** @type {{attach?: function, detach?: function, resize?: function}} */ - this._listeners = {}; - this._sortedMetasets = []; - this.scales = {}; - this.scale = undefined; - this._plugins = new PluginService(); - this.$proxies = {}; - this._hiddenIndices = {}; - this.attached = false; - this._animationsDisabled = undefined; - this.$context = undefined; - - // Add the chart instance to the global namespace - Chart.instances[me.id] = me; - - if (!context || !canvas) { - // The given item is not a compatible context2d element, let's return before finalizing - // the chart initialization but after setting basic chart / controller properties that - // can help to figure out that the chart is not valid (e.g chart.canvas !== null); - // https://github.com/chartjs/Chart.js/issues/2807 - console.error("Failed to create chart: can't acquire context from the given item"); - return; - } - - animator.listen(me, 'complete', onAnimationsComplete); - animator.listen(me, 'progress', onAnimationProgress); - - me._initialize(); - if (me.attached) { - me.update(); - } - } - - get data() { - return this.config.data; - } - - set data(data) { - this.config.data = data; - } - - /** + ); + } + + this.platform = me._initializePlatform(initialCanvas, config); + + const context = me.platform.acquireContext(initialCanvas, config); + const canvas = context && context.canvas; + const height = canvas && canvas.height; + const width = canvas && canvas.width; + + this.id = uid(); + this.ctx = context; + this.canvas = canvas; + this.width = width; + this.height = height; + this.aspectRatio = height ? width / height : null; + this.options = config.options; + this._layers = []; + this._metasets = []; + this.boxes = []; + this.currentDevicePixelRatio = undefined; + this.chartArea = undefined; + this._active = []; + this._lastEvent = undefined; + /** @type {{attach?: function, detach?: function, resize?: function}} */ + this._listeners = {}; + this._sortedMetasets = []; + this.scales = {}; + this.scale = undefined; + this._plugins = new PluginService(); + this.$proxies = {}; + this._hiddenIndices = {}; + this.attached = false; + this._animationsDisabled = undefined; + this.$context = undefined; + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + animator.listen(me, 'complete', onAnimationsComplete); + animator.listen(me, 'progress', onAnimationProgress); + + me._initialize(); + if (me.attached) { + me.update(); + } + } + + get data() { + return this.config.data; + } + + set data(data) { + this.config.data = data; + } + + /** * @private */ - _initialize() { - const me = this; + _initialize() { + const me = this; - // Before init plugin notification - me.notifyPlugins('beforeInit'); + // Before init plugin notification + me.notifyPlugins('beforeInit'); - if (me.options.responsive) { - me.resize(); - } else { - retinaScale(me, me.options.devicePixelRatio); - } + if (me.options.responsive) { + me.resize(); + } else { + retinaScale(me, me.options.devicePixelRatio); + } - me.bindEvents(); + me.bindEvents(); - // After init plugin notification - me.notifyPlugins('afterInit'); + // After init plugin notification + me.notifyPlugins('afterInit'); - return me; - } + return me; + } - /** + /** * @private */ - _initializePlatform(canvas, config) { - if (config.platform) { - return new config.platform(); - } else if (!isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) { - return new BasicPlatform(); - } - return new DomPlatform(); - } - - clear() { - clearCanvas(this.canvas, this.ctx); - return this; - } - - stop() { - animator.stop(this); - return this; - } - - /** + _initializePlatform(canvas, config) { + if (config.platform) { + return new config.platform(); + } else if (!isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) { + return new BasicPlatform(); + } + return new DomPlatform(); + } + + clear() { + clearCanvas(this.canvas, this.ctx); + return this; + } + + stop() { + animator.stop(this); + return this; + } + + /** * Resize the chart to its container or to explicit dimensions. * @param {number} [width] * @param {number} [height] */ - resize(width, height) { - if (!animator.running(this)) { - this._resize(width, height); - } else { - this._resizeBeforeDraw = {width, height}; - } - } - - _resize(width, height) { - const me = this; - const options = me.options; - const canvas = me.canvas; - const aspectRatio = options.maintainAspectRatio && me.aspectRatio; - const newSize = me.platform.getMaximumSize(canvas, width, height, aspectRatio); - - // detect devicePixelRation changes - const oldRatio = me.currentDevicePixelRatio; - const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio(); - - if (me.width === newSize.width && me.height === newSize.height && oldRatio === newRatio) { - return; - } - - canvas.width = me.width = newSize.width; - canvas.height = me.height = newSize.height; - if (canvas.style) { - canvas.style.width = newSize.width + 'px'; - canvas.style.height = newSize.height + 'px'; - } - - retinaScale(me, newRatio); - - me.notifyPlugins('resize', {size: newSize}); - - callCallback(options.onResize, [newSize], me); - - if (me.attached) { - me.update('resize'); - } - } - - ensureScalesHaveIDs() { - const options = this.options; - const scalesOptions = options.scales || {}; - const scaleOptions = options.scale; - - each(scalesOptions, (axisOptions, axisID) => { - axisOptions.id = axisID; - }); - - if (scaleOptions) { - scaleOptions.id = scaleOptions.id || 'scale'; - } - } - - /** + resize(width, height) { + if (!animator.running(this)) { + this._resize(width, height); + } else { + this._resizeBeforeDraw = {width, height}; + } + } + + _resize(width, height) { + const me = this; + const options = me.options; + const canvas = me.canvas; + const aspectRatio = options.maintainAspectRatio && me.aspectRatio; + const newSize = me.platform.getMaximumSize(canvas, width, height, aspectRatio); + + // detect devicePixelRation changes + const oldRatio = me.currentDevicePixelRatio; + const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio(); + + if (me.width === newSize.width && me.height === newSize.height && oldRatio === newRatio) { + return; + } + + canvas.width = me.width = newSize.width; + canvas.height = me.height = newSize.height; + if (canvas.style) { + canvas.style.width = newSize.width + 'px'; + canvas.style.height = newSize.height + 'px'; + } + + retinaScale(me, newRatio); + + me.notifyPlugins('resize', {size: newSize}); + + callCallback(options.onResize, [newSize], me); + + if (me.attached) { + me.update('resize'); + } + } + + ensureScalesHaveIDs() { + const options = this.options; + const scalesOptions = options.scales || {}; + const scaleOptions = options.scale; + + each(scalesOptions, (axisOptions, axisID) => { + axisOptions.id = axisID; + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + } + + /** * Builds a map of scale ID to scale object for future lookup. */ - buildOrUpdateScales() { - const me = this; - const options = me.options; - const scaleOpts = options.scales; - const scales = me.scales || {}; - const updated = Object.keys(scales).reduce((obj, id) => { - obj[id] = false; - return obj; - }, {}); - let items = []; - - if (scaleOpts) { - items = items.concat( - Object.keys(scaleOpts).map((id) => { - const scaleOptions = scaleOpts[id]; - const axis = determineAxis(id, scaleOptions); - const isRadial = axis === 'r'; - const isHorizontal = axis === 'x'; - return { - options: scaleOptions, - dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left', - dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear' - }; - }) - ); - } - - each(items, (item) => { - const scaleOptions = item.options; - const id = scaleOptions.id; - const axis = determineAxis(id, scaleOptions); - const scaleType = valueOrDefault(scaleOptions.type, item.dtype); - - if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) { - scaleOptions.position = item.dposition; - } - - updated[id] = true; - let scale = null; - if (id in scales && scales[id].type === scaleType) { - scale = scales[id]; - } else { - const scaleClass = registry.getScale(scaleType); - scale = new scaleClass({ - id, - type: scaleType, - ctx: me.ctx, - chart: me - }); - scales[scale.id] = scale; - } - - scale.init(scaleOptions, options); - }); - // clear up discarded scales - each(updated, (hasUpdated, id) => { - if (!hasUpdated) { - delete scales[id]; - } - }); - - me.scales = scales; - - each(scales, (scale) => { - layouts.configure(me, scale, scale.options); - layouts.addBox(me, scale); - }); - } - - /** + buildOrUpdateScales() { + const me = this; + const options = me.options; + const scaleOpts = options.scales; + const scales = me.scales || {}; + const updated = Object.keys(scales).reduce((obj, id) => { + obj[id] = false; + return obj; + }, {}); + let items = []; + + if (scaleOpts) { + items = items.concat( + Object.keys(scaleOpts).map((id) => { + const scaleOptions = scaleOpts[id]; + const axis = determineAxis(id, scaleOptions); + const isRadial = axis === 'r'; + const isHorizontal = axis === 'x'; + return { + options: scaleOptions, + dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left', + dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear' + }; + }) + ); + } + + each(items, (item) => { + const scaleOptions = item.options; + const id = scaleOptions.id; + const axis = determineAxis(id, scaleOptions); + const scaleType = valueOrDefault(scaleOptions.type, item.dtype); + + if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + updated[id] = true; + let scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + } else { + const scaleClass = registry.getScale(scaleType); + scale = new scaleClass({ + id, + type: scaleType, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } + + scale.init(scaleOptions, options); + }); + // clear up discarded scales + each(updated, (hasUpdated, id) => { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; + + each(scales, (scale) => { + layouts.configure(me, scale, scale.options); + layouts.addBox(me, scale); + }); + } + + /** * Updates the given metaset with the given dataset index. Ensures it's stored at that index * in the _metasets array by swapping with the metaset at that index if necessary. * @param {Object} meta - the dataset metadata * @param {number} index - the dataset index * @private */ - _updateMetasetIndex(meta, index) { - const metasets = this._metasets; - const oldIndex = meta.index; - if (oldIndex !== index) { - metasets[oldIndex] = metasets[index]; - metasets[index] = meta; - meta.index = index; - } - } - - /** + _updateMetasetIndex(meta, index) { + const metasets = this._metasets; + const oldIndex = meta.index; + if (oldIndex !== index) { + metasets[oldIndex] = metasets[index]; + metasets[index] = meta; + meta.index = index; + } + } + + /** * @private */ - _updateMetasets() { - const me = this; - const metasets = me._metasets; - const numData = me.data.datasets.length; - const numMeta = metasets.length; - - if (numMeta > numData) { - for (let i = numData; i < numMeta; ++i) { - me._destroyDatasetMeta(i); - } - metasets.splice(numData, numMeta - numData); - } - me._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index')); - } - - /** + _updateMetasets() { + const me = this; + const metasets = me._metasets; + const numData = me.data.datasets.length; + const numMeta = metasets.length; + + if (numMeta > numData) { + for (let i = numData; i < numMeta; ++i) { + me._destroyDatasetMeta(i); + } + metasets.splice(numData, numMeta - numData); + } + me._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index')); + } + + /** * @private */ - _removeUnreferencedMetasets() { - const me = this; - const datasets = me.data.datasets; - me._metasets.forEach((meta, index) => { - if (datasets.filter(x => x === meta._dataset).length === 0) { - me._destroyDatasetMeta(index); - } - }); - } - - buildOrUpdateControllers() { - const me = this; - const newControllers = []; - const datasets = me.data.datasets; - let i, ilen; - - me._removeUnreferencedMetasets(); - - for (i = 0, ilen = datasets.length; i < ilen; i++) { - const dataset = datasets[i]; - let meta = me.getDatasetMeta(i); - const type = dataset.type || me.config.type; - - if (meta.type && meta.type !== type) { - me._destroyDatasetMeta(i); - meta = me.getDatasetMeta(i); - } - meta.type = type; - meta.indexAxis = dataset.indexAxis || getIndexAxis(type, me.options); - meta.order = dataset.order || 0; - me._updateMetasetIndex(meta, i); - meta.label = '' + dataset.label; - meta.visible = me.isDatasetVisible(i); - - if (meta.controller) { - meta.controller.updateIndex(i); - meta.controller.linkScales(); - } else { - const controllerDefaults = defaults.controllers[type]; - const ControllerClass = registry.getController(type); - Object.assign(ControllerClass.prototype, { - dataElementType: registry.getElement(controllerDefaults.dataElementType), - datasetElementType: controllerDefaults.datasetElementType && registry.getElement(controllerDefaults.datasetElementType), - dataElementOptions: controllerDefaults.dataElementOptions, - datasetElementOptions: controllerDefaults.datasetElementOptions - }); - meta.controller = new ControllerClass(me, i); - newControllers.push(meta.controller); - } - } - - me._updateMetasets(); - return newControllers; - } - - /** + _removeUnreferencedMetasets() { + const me = this; + const datasets = me.data.datasets; + me._metasets.forEach((meta, index) => { + if (datasets.filter(x => x === meta._dataset).length === 0) { + me._destroyDatasetMeta(index); + } + }); + } + + buildOrUpdateControllers() { + const me = this; + const newControllers = []; + const datasets = me.data.datasets; + let i, ilen; + + me._removeUnreferencedMetasets(); + + for (i = 0, ilen = datasets.length; i < ilen; i++) { + const dataset = datasets[i]; + let meta = me.getDatasetMeta(i); + const type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me._destroyDatasetMeta(i); + meta = me.getDatasetMeta(i); + } + meta.type = type; + meta.indexAxis = dataset.indexAxis || getIndexAxis(type, me.options); + meta.order = dataset.order || 0; + me._updateMetasetIndex(meta, i); + meta.label = '' + dataset.label; + meta.visible = me.isDatasetVisible(i); + + if (meta.controller) { + meta.controller.updateIndex(i); + meta.controller.linkScales(); + } else { + const controllerDefaults = defaults.controllers[type]; + const ControllerClass = registry.getController(type); + Object.assign(ControllerClass.prototype, { + dataElementType: registry.getElement(controllerDefaults.dataElementType), + datasetElementType: controllerDefaults.datasetElementType && registry.getElement(controllerDefaults.datasetElementType), + dataElementOptions: controllerDefaults.dataElementOptions, + datasetElementOptions: controllerDefaults.datasetElementOptions + }); + meta.controller = new ControllerClass(me, i); + newControllers.push(meta.controller); + } + } + + me._updateMetasets(); + return newControllers; + } + + /** * Reset the elements of all datasets * @private */ - _resetElements() { - const me = this; - each(me.data.datasets, (dataset, datasetIndex) => { - me.getDatasetMeta(datasetIndex).controller.reset(); - }, me); - } - - /** + _resetElements() { + const me = this; + each(me.data.datasets, (dataset, datasetIndex) => { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + } + + /** * Resets the chart back to its state before the initial animation */ - reset() { - this._resetElements(); - this.notifyPlugins('reset'); - } + reset() { + this._resetElements(); + this.notifyPlugins('reset'); + } - update(mode) { - const me = this; - let i, ilen; + update(mode) { + const me = this; + let i, ilen; - each(me.scales, (scale) => { - layouts.removeBox(me, scale); - }); + each(me.scales, (scale) => { + layouts.removeBox(me, scale); + }); - me.config.update(me.options); - me.options = me.config.options; - const animsDisabled = me._animationsDisabled = !me.options.animation; + me.config.update(me.options); + me.options = me.config.options; + const animsDisabled = me._animationsDisabled = !me.options.animation; - me.ensureScalesHaveIDs(); - me.buildOrUpdateScales(); + me.ensureScalesHaveIDs(); + me.buildOrUpdateScales(); - // plugins options references might have change, let's invalidate the cache - // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - me._plugins.invalidate(); + // plugins options references might have change, let's invalidate the cache + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + me._plugins.invalidate(); - if (me.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) { - return; - } + if (me.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) { + return; + } - // Make sure dataset controllers are updated and new controllers are reset - const newControllers = me.buildOrUpdateControllers(); + // Make sure dataset controllers are updated and new controllers are reset + const newControllers = me.buildOrUpdateControllers(); - me.notifyPlugins('beforeElementsUpdate'); + me.notifyPlugins('beforeElementsUpdate'); - // Make sure all dataset controllers have correct meta data counts - for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { - const {controller} = me.getDatasetMeta(i); - const reset = !animsDisabled && newControllers.indexOf(controller) === -1; - // New controllers will be reset after the layout pass, so we only want to modify - // elements added to new datasets - controller.buildOrUpdateElements(reset); - } + // Make sure all dataset controllers have correct meta data counts + for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { + const {controller} = me.getDatasetMeta(i); + const reset = !animsDisabled && newControllers.indexOf(controller) === -1; + // New controllers will be reset after the layout pass, so we only want to modify + // elements added to new datasets + controller.buildOrUpdateElements(reset); + } - me._updateLayout(); + me._updateLayout(); - // Only reset the controllers if we have animations - if (!animsDisabled) { - // Can only reset the new controllers after the scales have been updated - // Reset is done to get the starting point for the initial animation - each(newControllers, (controller) => { - controller.reset(); - }); - } + // Only reset the controllers if we have animations + if (!animsDisabled) { + // Can only reset the new controllers after the scales have been updated + // Reset is done to get the starting point for the initial animation + each(newControllers, (controller) => { + controller.reset(); + }); + } - me._updateDatasets(mode); + me._updateDatasets(mode); - // Do this before render so that any plugins that need final scale updates can use it - me.notifyPlugins('afterUpdate', {mode}); + // Do this before render so that any plugins that need final scale updates can use it + me.notifyPlugins('afterUpdate', {mode}); - me._layers.sort(compare2Level('z', '_idx')); + me._layers.sort(compare2Level('z', '_idx')); - // Replay last event from before update - if (me._lastEvent) { - me._eventHandler(me._lastEvent, true); - } + // Replay last event from before update + if (me._lastEvent) { + me._eventHandler(me._lastEvent, true); + } - me.render(); - } + me.render(); + } - /** + /** * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` * hook, in which case, plugins will not be called on `afterLayout`. * @private */ - _updateLayout() { - const me = this; + _updateLayout() { + const me = this; - if (me.notifyPlugins('beforeLayout', {cancelable: true}) === false) { - return; - } + if (me.notifyPlugins('beforeLayout', {cancelable: true}) === false) { + return; + } - layouts.update(me, me.width, me.height); + layouts.update(me, me.width, me.height); - const area = me.chartArea; - const noArea = area.width <= 0 || area.height <= 0; + const area = me.chartArea; + const noArea = area.width <= 0 || area.height <= 0; - me._layers = []; - each(me.boxes, (box) => { - if (noArea && box.position === 'chartArea') { - // Skip drawing and configuring chartArea boxes when chartArea is zero or negative - return; - } + me._layers = []; + each(me.boxes, (box) => { + if (noArea && box.position === 'chartArea') { + // Skip drawing and configuring chartArea boxes when chartArea is zero or negative + return; + } - // configure is called twice, once in core.scale.update and once here. - // Here the boxes are fully updated and at their final positions. - if (box.configure) { - box.configure(); - } - me._layers.push(...box._layers()); - }, me); + // configure is called twice, once in core.scale.update and once here. + // Here the boxes are fully updated and at their final positions. + if (box.configure) { + box.configure(); + } + me._layers.push(...box._layers()); + }, me); - me._layers.forEach((item, index) => { - item._idx = index; - }); + me._layers.forEach((item, index) => { + item._idx = index; + }); - me.notifyPlugins('afterLayout'); - } + me.notifyPlugins('afterLayout'); + } - /** + /** * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. * @private */ - _updateDatasets(mode) { - const me = this; - const isFunction = typeof mode === 'function'; + _updateDatasets(mode) { + const me = this; + const isFunction = typeof mode === 'function'; - if (me.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) { - return; - } + if (me.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) { + return; + } - for (let i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me._updateDataset(i, isFunction ? mode({datasetIndex: i}) : mode); - } + for (let i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me._updateDataset(i, isFunction ? mode({datasetIndex: i}) : mode); + } - me.notifyPlugins('afterDatasetsUpdate', {mode}); - } + me.notifyPlugins('afterDatasetsUpdate', {mode}); + } - /** + /** * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` * hook, in which case, plugins will not be called on `afterDatasetUpdate`. * @private */ - _updateDataset(index, mode) { - const me = this; - const meta = me.getDatasetMeta(index); - const args = {meta, index, mode, cancelable: true}; - - if (me.notifyPlugins('beforeDatasetUpdate', args) === false) { - return; - } - - meta.controller._update(mode); - - args.cancelable = false; - me.notifyPlugins('afterDatasetUpdate', args); - } - - render() { - const me = this; - if (me.notifyPlugins('beforeRender', {cancelable: true}) === false) { - return; - } - - if (animator.has(me)) { - if (me.attached && !animator.running(me)) { - animator.start(me); - } - } else { - me.draw(); - onAnimationsComplete({chart: me}); - } - } - - draw() { - const me = this; - let i; - if (me._resizeBeforeDraw) { - const {width, height} = me._resizeBeforeDraw; - me._resize(width, height); - me._resizeBeforeDraw = null; - } - me.clear(); - - if (me.width <= 0 || me.height <= 0) { - return; - } - - if (me.notifyPlugins('beforeDraw', {cancelable: true}) === false) { - return; - } - - // Because of plugin hooks (before/afterDatasetsDraw), datasets can't - // currently be part of layers. Instead, we draw - // layers <= 0 before(default, backward compat), and the rest after - const layers = me._layers; - for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { - layers[i].draw(me.chartArea); - } - - me._drawDatasets(); - - // Rest of layers - for (; i < layers.length; ++i) { - layers[i].draw(me.chartArea); - } - - me.notifyPlugins('afterDraw'); - } - - /** + _updateDataset(index, mode) { + const me = this; + const meta = me.getDatasetMeta(index); + const args = {meta, index, mode, cancelable: true}; + + if (me.notifyPlugins('beforeDatasetUpdate', args) === false) { + return; + } + + meta.controller._update(mode); + + args.cancelable = false; + me.notifyPlugins('afterDatasetUpdate', args); + } + + render() { + const me = this; + if (me.notifyPlugins('beforeRender', {cancelable: true}) === false) { + return; + } + + if (animator.has(me)) { + if (me.attached && !animator.running(me)) { + animator.start(me); + } + } else { + me.draw(); + onAnimationsComplete({chart: me}); + } + } + + draw() { + const me = this; + let i; + if (me._resizeBeforeDraw) { + const {width, height} = me._resizeBeforeDraw; + me._resize(width, height); + me._resizeBeforeDraw = null; + } + me.clear(); + + if (me.width <= 0 || me.height <= 0) { + return; + } + + if (me.notifyPlugins('beforeDraw', {cancelable: true}) === false) { + return; + } + + // Because of plugin hooks (before/afterDatasetsDraw), datasets can't + // currently be part of layers. Instead, we draw + // layers <= 0 before(default, backward compat), and the rest after + const layers = me._layers; + for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { + layers[i].draw(me.chartArea); + } + + me._drawDatasets(); + + // Rest of layers + for (; i < layers.length; ++i) { + layers[i].draw(me.chartArea); + } + + me.notifyPlugins('afterDraw'); + } + + /** * @private */ - _getSortedDatasetMetas(filterVisible) { - const me = this; - const metasets = me._sortedMetasets; - const result = []; - let i, ilen; - - for (i = 0, ilen = metasets.length; i < ilen; ++i) { - const meta = metasets[i]; - if (!filterVisible || meta.visible) { - result.push(meta); - } - } - - return result; - } - - /** + _getSortedDatasetMetas(filterVisible) { + const me = this; + const metasets = me._sortedMetasets; + const result = []; + let i, ilen; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + const meta = metasets[i]; + if (!filterVisible || meta.visible) { + result.push(meta); + } + } + + return result; + } + + /** * Gets the visible dataset metas in drawing order * @return {object[]} */ - getSortedVisibleDatasetMetas() { - return this._getSortedDatasetMetas(true); - } + getSortedVisibleDatasetMetas() { + return this._getSortedDatasetMetas(true); + } - /** + /** * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` * hook, in which case, plugins will not be called on `afterDatasetsDraw`. * @private */ - _drawDatasets() { - const me = this; + _drawDatasets() { + const me = this; - if (me.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) { - return; - } + if (me.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) { + return; + } - const metasets = me.getSortedVisibleDatasetMetas(); - for (let i = metasets.length - 1; i >= 0; --i) { - me._drawDataset(metasets[i]); - } + const metasets = me.getSortedVisibleDatasetMetas(); + for (let i = metasets.length - 1; i >= 0; --i) { + me._drawDataset(metasets[i]); + } - me.notifyPlugins('afterDatasetsDraw'); - } + me.notifyPlugins('afterDatasetsDraw'); + } - /** + /** * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` * hook, in which case, plugins will not be called on `afterDatasetDraw`. * @private */ - _drawDataset(meta) { - const me = this; - const ctx = me.ctx; - const clip = meta._clip; - const area = me.chartArea; - const args = { - meta, - index: meta.index, - cancelable: true - }; - - if (me.notifyPlugins('beforeDatasetDraw', args) === false) { - return; - } - - clipArea(ctx, { - left: clip.left === false ? 0 : area.left - clip.left, - right: clip.right === false ? me.width : area.right + clip.right, - top: clip.top === false ? 0 : area.top - clip.top, - bottom: clip.bottom === false ? me.height : area.bottom + clip.bottom - }); - - meta.controller.draw(); - - unclipArea(ctx); - - args.cancelable = false; - me.notifyPlugins('afterDatasetDraw', args); - } - - getElementsAtEventForMode(e, mode, options, useFinalPosition) { - const method = Interaction.modes[mode]; - if (typeof method === 'function') { - return method(this, e, options, useFinalPosition); - } - - return []; - } - - getDatasetMeta(datasetIndex) { - const me = this; - const dataset = me.data.datasets[datasetIndex]; - const metasets = me._metasets; - let meta = metasets.filter(x => x && x._dataset === dataset).pop(); - - if (!meta) { - meta = metasets[datasetIndex] = { - type: null, - data: [], - dataset: null, - controller: null, - hidden: null, // See isDatasetVisible() comment - xAxisID: null, - yAxisID: null, - order: dataset && dataset.order || 0, - index: datasetIndex, - _dataset: dataset, - _parsed: [], - _sorted: false - }; - } - - return meta; - } - - getContext() { - return this.$context || (this.$context = Object.create(null, { - chart: { - value: this - }, - type: { - value: 'chart' - } - })); - } - - getVisibleDatasetCount() { - return this.getSortedVisibleDatasetMetas().length; - } - - isDatasetVisible(datasetIndex) { - const dataset = this.data.datasets[datasetIndex]; - if (!dataset) { - return false; - } - - const meta = this.getDatasetMeta(datasetIndex); - - // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, - // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. - return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden; - } - - setDatasetVisibility(datasetIndex, visible) { - const meta = this.getDatasetMeta(datasetIndex); - meta.hidden = !visible; - } - - toggleDataVisibility(index) { - this._hiddenIndices[index] = !this._hiddenIndices[index]; - } - - getDataVisibility(index) { - return !this._hiddenIndices[index]; - } - - /** + _drawDataset(meta) { + const me = this; + const ctx = me.ctx; + const clip = meta._clip; + const area = me.chartArea; + const args = { + meta, + index: meta.index, + cancelable: true + }; + + if (me.notifyPlugins('beforeDatasetDraw', args) === false) { + return; + } + + clipArea(ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? me.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? me.height : area.bottom + clip.bottom + }); + + meta.controller.draw(); + + unclipArea(ctx); + + args.cancelable = false; + me.notifyPlugins('afterDatasetDraw', args); + } + + getElementsAtEventForMode(e, mode, options, useFinalPosition) { + const method = Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options, useFinalPosition); + } + + return []; + } + + getDatasetMeta(datasetIndex) { + const me = this; + const dataset = me.data.datasets[datasetIndex]; + const metasets = me._metasets; + let meta = metasets.filter(x => x && x._dataset === dataset).pop(); + + if (!meta) { + meta = metasets[datasetIndex] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null, + order: dataset && dataset.order || 0, + index: datasetIndex, + _dataset: dataset, + _parsed: [], + _sorted: false + }; + } + + return meta; + } + + getContext() { + return this.$context || (this.$context = Object.create(null, { + chart: { + value: this + }, + type: { + value: 'chart' + } + })); + } + + getVisibleDatasetCount() { + return this.getSortedVisibleDatasetMetas().length; + } + + isDatasetVisible(datasetIndex) { + const dataset = this.data.datasets[datasetIndex]; + if (!dataset) { + return false; + } + + const meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden; + } + + setDatasetVisibility(datasetIndex, visible) { + const meta = this.getDatasetMeta(datasetIndex); + meta.hidden = !visible; + } + + toggleDataVisibility(index) { + this._hiddenIndices[index] = !this._hiddenIndices[index]; + } + + getDataVisibility(index) { + return !this._hiddenIndices[index]; + } + + /** * @private */ - _updateDatasetVisibility(datasetIndex, visible) { - const me = this; - const mode = visible ? 'show' : 'hide'; - const meta = me.getDatasetMeta(datasetIndex); - const anims = meta.controller._resolveAnimations(undefined, mode); - me.setDatasetVisibility(datasetIndex, visible); + _updateDatasetVisibility(datasetIndex, visible) { + const me = this; + const mode = visible ? 'show' : 'hide'; + const meta = me.getDatasetMeta(datasetIndex); + const anims = meta.controller._resolveAnimations(undefined, mode); + me.setDatasetVisibility(datasetIndex, visible); - // Animate visible state, so hide animation can be seen. This could be handled better if update / updateDataset returned a Promise. - anims.update(meta, {visible}); + // Animate visible state, so hide animation can be seen. This could be handled better if update / updateDataset returned a Promise. + anims.update(meta, {visible}); - me.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined); - } + me.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined); + } - hide(datasetIndex) { - this._updateDatasetVisibility(datasetIndex, false); - } + hide(datasetIndex) { + this._updateDatasetVisibility(datasetIndex, false); + } - show(datasetIndex) { - this._updateDatasetVisibility(datasetIndex, true); - } + show(datasetIndex) { + this._updateDatasetVisibility(datasetIndex, true); + } - /** + /** * @private */ - _destroyDatasetMeta(datasetIndex) { - const me = this; - const meta = me._metasets && me._metasets[datasetIndex]; - - if (meta && meta.controller) { - meta.controller._destroy(); - delete me._metasets[datasetIndex]; - } - } - - destroy() { - const me = this; - const {canvas, ctx} = me; - let i, ilen; - - me.stop(); - animator.remove(me); - - // dataset controllers need to cleanup associated data - for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me._destroyDatasetMeta(i); - } - - if (canvas) { - me.unbindEvents(); - clearCanvas(canvas, ctx); - me.platform.releaseContext(ctx); - me.canvas = null; - me.ctx = null; - } - - me.notifyPlugins('destroy'); - - delete Chart.instances[me.id]; - } - - toBase64Image(...args) { - return this.canvas.toDataURL(...args); - } - - /** + _destroyDatasetMeta(datasetIndex) { + const me = this; + const meta = me._metasets && me._metasets[datasetIndex]; + + if (meta && meta.controller) { + meta.controller._destroy(); + delete me._metasets[datasetIndex]; + } + } + + destroy() { + const me = this; + const {canvas, ctx} = me; + let i, ilen; + + me.stop(); + animator.remove(me); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me._destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + clearCanvas(canvas, ctx); + me.platform.releaseContext(ctx); + me.canvas = null; + me.ctx = null; + } + + me.notifyPlugins('destroy'); + + delete Chart.instances[me.id]; + } + + toBase64Image(...args) { + return this.canvas.toDataURL(...args); + } + + /** * @private */ - bindEvents() { - const me = this; - const listeners = me._listeners; - const platform = me.platform; - - const _add = (type, listener) => { - platform.addEventListener(me, type, listener); - listeners[type] = listener; - }; - const _remove = (type, listener) => { - if (listeners[type]) { - platform.removeEventListener(me, type, listener); - delete listeners[type]; - } - }; - - let listener = function(e, x, y) { - e.offsetX = x; - e.offsetY = y; - me._eventHandler(e); - }; - - each(me.options.events, (type) => _add(type, listener)); - - if (me.options.responsive) { - listener = (width, height) => { - if (me.canvas) { - me.resize(width, height); - } - }; - - let detached; // eslint-disable-line prefer-const - const attached = () => { - _remove('attach', attached); - - me.attached = true; - me.resize(); - - _add('resize', listener); - _add('detach', detached); - }; - - detached = () => { - me.attached = false; - - _remove('resize', listener); - _add('attach', attached); - }; - - if (platform.isAttached(me.canvas)) { - attached(); - } else { - detached(); - } - } else { - me.attached = true; - } - } - - /** + bindEvents() { + const me = this; + const listeners = me._listeners; + const platform = me.platform; + + const _add = (type, listener) => { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }; + const _remove = (type, listener) => { + if (listeners[type]) { + platform.removeEventListener(me, type, listener); + delete listeners[type]; + } + }; + + let listener = function(e, x, y) { + e.offsetX = x; + e.offsetY = y; + me._eventHandler(e); + }; + + each(me.options.events, (type) => _add(type, listener)); + + if (me.options.responsive) { + listener = (width, height) => { + if (me.canvas) { + me.resize(width, height); + } + }; + + let detached; // eslint-disable-line prefer-const + const attached = () => { + _remove('attach', attached); + + me.attached = true; + me.resize(); + + _add('resize', listener); + _add('detach', detached); + }; + + detached = () => { + me.attached = false; + + _remove('resize', listener); + _add('attach', attached); + }; + + if (platform.isAttached(me.canvas)) { + attached(); + } else { + detached(); + } + } else { + me.attached = true; + } + } + + /** * @private */ - unbindEvents() { - const me = this; - const listeners = me._listeners; - if (!listeners) { - return; - } - - delete me._listeners; - each(listeners, (listener, type) => { - me.platform.removeEventListener(me, type, listener); - }); - } - - updateHoverStyle(items, mode, enabled) { - const prefix = enabled ? 'set' : 'remove'; - let meta, item, i, ilen; - - if (mode === 'dataset') { - meta = this.getDatasetMeta(items[0].datasetIndex); - meta.controller['_' + prefix + 'DatasetHoverStyle'](); - } - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - const controller = item && this.getDatasetMeta(item.datasetIndex).controller; - if (controller) { - controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index); - } - } - } - - /** + unbindEvents() { + const me = this; + const listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + each(listeners, (listener, type) => { + me.platform.removeEventListener(me, type, listener); + }); + } + + updateHoverStyle(items, mode, enabled) { + const prefix = enabled ? 'set' : 'remove'; + let meta, item, i, ilen; + + if (mode === 'dataset') { + meta = this.getDatasetMeta(items[0].datasetIndex); + meta.controller['_' + prefix + 'DatasetHoverStyle'](); + } + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + const controller = item && this.getDatasetMeta(item.datasetIndex).controller; + if (controller) { + controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index); + } + } + } + + /** * Get active (hovered) elements * @returns array */ - getActiveElements() { - return this._active || []; - } + getActiveElements() { + return this._active || []; + } - /** + /** * Set active (hovered) elements * @param {array} activeElements New active data points */ - setActiveElements(activeElements) { - const me = this; - const lastActive = me._active || []; - const active = activeElements.map(({datasetIndex, index}) => { - const meta = me.getDatasetMeta(datasetIndex); - if (!meta) { - throw new Error('No dataset found at index ' + datasetIndex); - } - - return { - datasetIndex, - element: meta.data[index], - index, - }; - }); - const changed = !_elementsEqual(active, lastActive); - - if (changed) { - me._active = active; - me._updateHoverStyles(active, lastActive); - } - } - - /** + setActiveElements(activeElements) { + const me = this; + const lastActive = me._active || []; + const active = activeElements.map(({datasetIndex, index}) => { + const meta = me.getDatasetMeta(datasetIndex); + if (!meta) { + throw new Error('No dataset found at index ' + datasetIndex); + } + + return { + datasetIndex, + element: meta.data[index], + index, + }; + }); + const changed = !_elementsEqual(active, lastActive); + + if (changed) { + me._active = active; + me._updateHoverStyles(active, lastActive); + } + } + + /** * Calls enabled plugins on the specified hook and with the given args. * This method immediately returns as soon as a plugin explicitly returns false. The * returned value can be used, for instance, to interrupt the current action. @@ -991,109 +991,109 @@ class Chart { * @param {Object} [args] - Extra arguments to apply to the hook call. * @returns {boolean} false if any of the plugins return false, else returns true. */ - notifyPlugins(hook, args) { - return this._plugins.notify(this, hook, args); - } + notifyPlugins(hook, args) { + return this._plugins.notify(this, hook, args); + } - /** + /** * @private */ - _updateHoverStyles(active, lastActive, replay) { - const me = this; - const options = me.options || {}; - const hoverOptions = options.hover; - const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index)); - const deactivated = diff(lastActive, active); - const activated = replay ? active : diff(active, lastActive); - - if (deactivated.length) { - me.updateHoverStyle(deactivated, hoverOptions.mode, false); - } - - if (activated.length && hoverOptions.mode) { - me.updateHoverStyle(activated, hoverOptions.mode, true); - } - } - - /** + _updateHoverStyles(active, lastActive, replay) { + const me = this; + const options = me.options || {}; + const hoverOptions = options.hover; + const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index)); + const deactivated = diff(lastActive, active); + const activated = replay ? active : diff(active, lastActive); + + if (deactivated.length) { + me.updateHoverStyle(deactivated, hoverOptions.mode, false); + } + + if (activated.length && hoverOptions.mode) { + me.updateHoverStyle(activated, hoverOptions.mode, true); + } + } + + /** * @private */ - _eventHandler(e, replay) { - const me = this; - const args = {event: e, replay, cancelable: true}; + _eventHandler(e, replay) { + const me = this; + const args = {event: e, replay, cancelable: true}; - if (me.notifyPlugins('beforeEvent', args) === false) { - return; - } + if (me.notifyPlugins('beforeEvent', args) === false) { + return; + } - const changed = me._handleEvent(e, replay); + const changed = me._handleEvent(e, replay); - args.cancelable = false; - me.notifyPlugins('afterEvent', args); + args.cancelable = false; + me.notifyPlugins('afterEvent', args); - if (changed || args.changed) { - me.render(); - } + if (changed || args.changed) { + me.render(); + } - return me; - } + return me; + } - /** + /** * Handle an event * @param {ChartEvent} e the event to handle * @param {boolean} [replay] - true if the event was replayed by `update` * @return {boolean} true if the chart needs to re-render * @private */ - _handleEvent(e, replay) { - const me = this; - const lastActive = me._active || []; - const options = me.options; - const hoverOptions = options.hover; - - // If the event is replayed from `update`, we should evaluate with the final positions. - // - // The `replay`: - // It's the last event (excluding click) that has occurred before `update`. - // So mouse has not moved. It's also over the chart, because there is a `replay`. - // - // The why: - // If animations are active, the elements haven't moved yet compared to state before update. - // But if they will, we are activating the elements that would be active, if this check - // was done after the animations have completed. => "final positions". - // If there is no animations, the "final" and "current" positions are equal. - // This is done so we do not have to evaluate the active elements each animation frame - // - it would be expensive. - const useFinalPosition = replay; - - let active = []; - let changed = false; - - // Find Active Elements for hover and tooltips - if (e.type === 'mouseout') { - me._lastEvent = null; - } else { - active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition); - me._lastEvent = e.type === 'click' ? me._lastEvent : e; - } - - // Invoke onHover hook - callCallback(options.onHover || options.hover.onHover, [e, active, me], me); - - if (e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu') { - if (_isPointInArea(e, me.chartArea)) { - callCallback(options.onClick, [e, active, me], me); - } - } - - changed = !_elementsEqual(active, lastActive); - if (changed || replay) { - me._active = active; - me._updateHoverStyles(active, lastActive, replay); - } - - return changed; - } + _handleEvent(e, replay) { + const me = this; + const lastActive = me._active || []; + const options = me.options; + const hoverOptions = options.hover; + + // If the event is replayed from `update`, we should evaluate with the final positions. + // + // The `replay`: + // It's the last event (excluding click) that has occurred before `update`. + // So mouse has not moved. It's also over the chart, because there is a `replay`. + // + // The why: + // If animations are active, the elements haven't moved yet compared to state before update. + // But if they will, we are activating the elements that would be active, if this check + // was done after the animations have completed. => "final positions". + // If there is no animations, the "final" and "current" positions are equal. + // This is done so we do not have to evaluate the active elements each animation frame + // - it would be expensive. + const useFinalPosition = replay; + + let active = []; + let changed = false; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me._lastEvent = null; + } else { + active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition); + me._lastEvent = e.type === 'click' ? me._lastEvent : e; + } + + // Invoke onHover hook + callCallback(options.onHover || options.hover.onHover, [e, active, me], me); + + if (e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu') { + if (_isPointInArea(e, me.chartArea)) { + callCallback(options.onClick, [e, active, me], me); + } + } + + changed = !_elementsEqual(active, lastActive); + if (changed || replay) { + me._active = active; + me._updateHoverStyles(active, lastActive, replay); + } + + return changed; + } } // These are available to both, UMD and ESM packages @@ -1103,20 +1103,20 @@ Chart.registry = registry; Chart.version = version; Chart.getChart = (key) => { - const canvas = getCanvas(key); - return Object.values(Chart.instances).filter((c) => c.canvas === canvas).pop(); + const canvas = getCanvas(key); + return Object.values(Chart.instances).filter((c) => c.canvas === canvas).pop(); }; // @ts-ignore const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate()); Chart.register = (...items) => { - registry.add(...items); - invalidatePlugins(); + registry.add(...items); + invalidatePlugins(); }; Chart.unregister = (...items) => { - registry.remove(...items); - invalidatePlugins(); + registry.remove(...items); + invalidatePlugins(); }; export default Chart; diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 49413ce9387..b5f50fd091c 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -12,202 +12,202 @@ import {sign} from '../helpers/helpers.math'; */ function scaleClip(scale, allowedOverflow) { - const opts = scale && scale.options || {}; - const reverse = opts.reverse; - const min = opts.min === undefined ? allowedOverflow : 0; - const max = opts.max === undefined ? allowedOverflow : 0; - return { - start: reverse ? max : min, - end: reverse ? min : max - }; + const opts = scale && scale.options || {}; + const reverse = opts.reverse; + const min = opts.min === undefined ? allowedOverflow : 0; + const max = opts.max === undefined ? allowedOverflow : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; } function defaultClip(xScale, yScale, allowedOverflow) { - if (allowedOverflow === false) { - return false; - } - const x = scaleClip(xScale, allowedOverflow); - const y = scaleClip(yScale, allowedOverflow); - - return { - top: y.end, - right: x.end, - bottom: y.start, - left: x.start - }; + if (allowedOverflow === false) { + return false; + } + const x = scaleClip(xScale, allowedOverflow); + const y = scaleClip(yScale, allowedOverflow); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; } function toClip(value) { - let t, r, b, l; - - if (isObject(value)) { - t = value.top; - r = value.right; - b = value.bottom; - l = value.left; - } else { - t = r = b = l = value; - } - - return { - top: t, - right: r, - bottom: b, - left: l - }; + let t, r, b, l; + + if (isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; } function getSortedDatasetIndices(chart, filterVisible) { - const keys = []; - const metasets = chart._getSortedDatasetMetas(filterVisible); - let i, ilen; - - for (i = 0, ilen = metasets.length; i < ilen; ++i) { - keys.push(metasets[i].index); - } - return keys; + const keys = []; + const metasets = chart._getSortedDatasetMetas(filterVisible); + let i, ilen; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + keys.push(metasets[i].index); + } + return keys; } function applyStack(stack, value, dsIndex, allOther) { - const keys = stack.keys; - let i, ilen, datasetIndex, otherValue; - - for (i = 0, ilen = keys.length; i < ilen; ++i) { - datasetIndex = +keys[i]; - if (datasetIndex === dsIndex) { - if (allOther) { - continue; - } - break; - } - otherValue = stack.values[datasetIndex]; - if (!isNaN(otherValue) && (value === 0 || sign(value) === sign(otherValue))) { - value += otherValue; - } - } - return value; + const keys = stack.keys; + let i, ilen, datasetIndex, otherValue; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + datasetIndex = +keys[i]; + if (datasetIndex === dsIndex) { + if (allOther) { + continue; + } + break; + } + otherValue = stack.values[datasetIndex]; + if (!isNaN(otherValue) && (value === 0 || sign(value) === sign(otherValue))) { + value += otherValue; + } + } + return value; } function convertObjectDataToArray(data) { - const keys = Object.keys(data); - const adata = new Array(keys.length); - let i, ilen, key; - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - adata[i] = { - x: key, - y: data[key] - }; - } - return adata; + const keys = Object.keys(data); + const adata = new Array(keys.length); + let i, ilen, key; + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + adata[i] = { + x: key, + y: data[key] + }; + } + return adata; } function isStacked(scale, meta) { - const stacked = scale && scale.options.stacked; - return stacked || (stacked === undefined && meta.stack !== undefined); + const stacked = scale && scale.options.stacked; + return stacked || (stacked === undefined && meta.stack !== undefined); } function getStackKey(indexScale, valueScale, meta) { - return indexScale.id + '.' + valueScale.id + '.' + meta.stack + '.' + meta.type; + return indexScale.id + '.' + valueScale.id + '.' + meta.stack + '.' + meta.type; } function getUserBounds(scale) { - const {min, max, minDefined, maxDefined} = scale.getUserBounds(); - return { - min: minDefined ? min : Number.NEGATIVE_INFINITY, - max: maxDefined ? max : Number.POSITIVE_INFINITY - }; + const {min, max, minDefined, maxDefined} = scale.getUserBounds(); + return { + min: minDefined ? min : Number.NEGATIVE_INFINITY, + max: maxDefined ? max : Number.POSITIVE_INFINITY + }; } function getOrCreateStack(stacks, stackKey, indexValue) { - const subStack = stacks[stackKey] || (stacks[stackKey] = {}); - return subStack[indexValue] || (subStack[indexValue] = {}); + const subStack = stacks[stackKey] || (stacks[stackKey] = {}); + return subStack[indexValue] || (subStack[indexValue] = {}); } function updateStacks(controller, parsed) { - const {chart, _cachedMeta: meta} = controller; - const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}} - const {iScale, vScale, index: datasetIndex} = meta; - const iAxis = iScale.axis; - const vAxis = vScale.axis; - const key = getStackKey(iScale, vScale, meta); - const ilen = parsed.length; - let stack; - - for (let i = 0; i < ilen; ++i) { - const item = parsed[i]; - const {[iAxis]: index, [vAxis]: value} = item; - const itemStacks = item._stacks || (item._stacks = {}); - stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index); - stack[datasetIndex] = value; - } + const {chart, _cachedMeta: meta} = controller; + const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}} + const {iScale, vScale, index: datasetIndex} = meta; + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const key = getStackKey(iScale, vScale, meta); + const ilen = parsed.length; + let stack; + + for (let i = 0; i < ilen; ++i) { + const item = parsed[i]; + const {[iAxis]: index, [vAxis]: value} = item; + const itemStacks = item._stacks || (item._stacks = {}); + stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index); + stack[datasetIndex] = value; + } } function getFirstScaleId(chart, axis) { - const scales = chart.scales; - return Object.keys(scales).filter(key => scales[key].axis === axis).shift(); + const scales = chart.scales; + return Object.keys(scales).filter(key => scales[key].axis === axis).shift(); } function createDatasetContext(parent, index, dataset) { - return Object.create(parent, { - active: { - writable: true, - value: false - }, - dataset: { - value: dataset - }, - datasetIndex: { - value: index - }, - index: { - get() { - return this.datasetIndex; - } - }, - type: { - value: 'dataset' - } - }); + return Object.create(parent, { + active: { + writable: true, + value: false + }, + dataset: { + value: dataset + }, + datasetIndex: { + value: index + }, + index: { + get() { + return this.datasetIndex; + } + }, + type: { + value: 'dataset' + } + }); } function createDataContext(parent, index, point, raw, element) { - return Object.create(parent, { - active: { - writable: true, - value: false - }, - dataIndex: { - value: index - }, - parsed: { - value: point - }, - raw: { - value: raw - }, - element: { - value: element - }, - index: { - get() { - return this.dataIndex; - } - }, - type: { - value: 'data', - } - }); + return Object.create(parent, { + active: { + writable: true, + value: false + }, + dataIndex: { + value: index + }, + parsed: { + value: point + }, + raw: { + value: raw + }, + element: { + value: element + }, + index: { + get() { + return this.dataIndex; + } + }, + type: { + value: 'data', + } + }); } function clearStacks(meta, items) { - items = items || meta._parsed; - items.forEach((parsed) => { - if (parsed._stacks[meta.vScale.id] === undefined || parsed._stacks[meta.vScale.id][meta.index] === undefined) { - return; - } - delete parsed._stacks[meta.vScale.id][meta.index]; - }); + items = items || meta._parsed; + items.forEach((parsed) => { + if (parsed._stacks[meta.vScale.id] === undefined || parsed._stacks[meta.vScale.id][meta.index] === undefined) { + return; + } + delete parsed._stacks[meta.vScale.id][meta.index]; + }); } const optionKeys = (optionNames) => isArray(optionNames) ? optionNames : Object.keys(optionNames); @@ -217,243 +217,243 @@ const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, export default class DatasetController { - /** + /** * @param {Chart} chart * @param {number} datasetIndex */ - constructor(chart, datasetIndex) { - this.chart = chart; - this._ctx = chart.ctx; - this.index = datasetIndex; - this._cachedAnimations = {}; - this._cachedDataOpts = {}; - this._cachedMeta = this.getMeta(); - this._type = this._cachedMeta.type; - this._config = undefined; - /** @type {boolean | object} */ - this._parsing = false; - this._data = undefined; - this._objectData = undefined; - this._sharedOptions = undefined; - this._drawStart = undefined; - this._drawCount = undefined; - this.enableOptionSharing = false; - this.$context = undefined; - - this.initialize(); - } - - initialize() { - const me = this; - const meta = me._cachedMeta; - me.configure(); - me.linkScales(); - meta._stacked = isStacked(meta.vScale, meta); - me.addElements(); - } - - updateIndex(datasetIndex) { - this.index = datasetIndex; - } - - linkScales() { - const me = this; - const chart = me.chart; - const meta = me._cachedMeta; - const dataset = me.getDataset(); - - const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y; - - const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x')); - const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y')); - const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r')); - const indexAxis = meta.indexAxis; - const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid); - const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid); - meta.xScale = me.getScaleForId(xid); - meta.yScale = me.getScaleForId(yid); - meta.rScale = me.getScaleForId(rid); - meta.iScale = me.getScaleForId(iid); - meta.vScale = me.getScaleForId(vid); - } - - getDataset() { - return this.chart.data.datasets[this.index]; - } - - getMeta() { - return this.chart.getDatasetMeta(this.index); - } - - /** + constructor(chart, datasetIndex) { + this.chart = chart; + this._ctx = chart.ctx; + this.index = datasetIndex; + this._cachedAnimations = {}; + this._cachedDataOpts = {}; + this._cachedMeta = this.getMeta(); + this._type = this._cachedMeta.type; + this._config = undefined; + /** @type {boolean | object} */ + this._parsing = false; + this._data = undefined; + this._objectData = undefined; + this._sharedOptions = undefined; + this._drawStart = undefined; + this._drawCount = undefined; + this.enableOptionSharing = false; + this.$context = undefined; + + this.initialize(); + } + + initialize() { + const me = this; + const meta = me._cachedMeta; + me.configure(); + me.linkScales(); + meta._stacked = isStacked(meta.vScale, meta); + me.addElements(); + } + + updateIndex(datasetIndex) { + this.index = datasetIndex; + } + + linkScales() { + const me = this; + const chart = me.chart; + const meta = me._cachedMeta; + const dataset = me.getDataset(); + + const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y; + + const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x')); + const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y')); + const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r')); + const indexAxis = meta.indexAxis; + const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid); + const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid); + meta.xScale = me.getScaleForId(xid); + meta.yScale = me.getScaleForId(yid); + meta.rScale = me.getScaleForId(rid); + meta.iScale = me.getScaleForId(iid); + meta.vScale = me.getScaleForId(vid); + } + + getDataset() { + return this.chart.data.datasets[this.index]; + } + + getMeta() { + return this.chart.getDatasetMeta(this.index); + } + + /** * @param {string} scaleID * @return {Scale} */ - getScaleForId(scaleID) { - return this.chart.scales[scaleID]; - } + getScaleForId(scaleID) { + return this.chart.scales[scaleID]; + } - /** + /** * @private */ - _getOtherScale(scale) { - const meta = this._cachedMeta; - return scale === meta.iScale - ? meta.vScale - : meta.iScale; - } - - reset() { - this._update('reset'); - } - - /** + _getOtherScale(scale) { + const meta = this._cachedMeta; + return scale === meta.iScale + ? meta.vScale + : meta.iScale; + } + + reset() { + this._update('reset'); + } + + /** * @private */ - _destroy() { - const meta = this._cachedMeta; - if (this._data) { - unlistenArrayEvents(this._data, this); - } - if (meta._stacked) { - clearStacks(meta); - } - } - - /** + _destroy() { + const meta = this._cachedMeta; + if (this._data) { + unlistenArrayEvents(this._data, this); + } + if (meta._stacked) { + clearStacks(meta); + } + } + + /** * @private */ - _dataCheck() { - const me = this; - const dataset = me.getDataset(); - const 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 (isObject(data)) { - me._data = convertObjectDataToArray(data); - } else if (me._data !== data) { - if (me._data) { - // This case happens when the user replaced the data array instance. - unlistenArrayEvents(me._data, me); - } - if (data && Object.isExtensible(data)) { - listenArrayEvents(data, me); - } - me._data = data; - } - } - - addElements() { - const me = this; - const meta = me._cachedMeta; - - me._dataCheck(); - - if (me.datasetElementType) { - meta.dataset = new me.datasetElementType(); - } - } - - buildOrUpdateElements(resetNewElements) { - const me = this; - const meta = me._cachedMeta; - const dataset = me.getDataset(); - let stackChanged = false; - - me._dataCheck(); - - // make sure cached _stacked status is current - meta._stacked = isStacked(meta.vScale, meta); - - // detect change in stack option - if (meta.stack !== dataset.stack) { - stackChanged = true; - // remove values from old stack - clearStacks(meta); - meta.stack = dataset.stack; - } - - // 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(resetNewElements); - - // if stack changed, update stack values for the whole dataset - if (stackChanged) { - updateStacks(me, meta._parsed); - } - } - - /** + _dataCheck() { + const me = this; + const dataset = me.getDataset(); + const 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 (isObject(data)) { + me._data = convertObjectDataToArray(data); + } else if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + } + + addElements() { + const me = this; + const meta = me._cachedMeta; + + me._dataCheck(); + + if (me.datasetElementType) { + meta.dataset = new me.datasetElementType(); + } + } + + buildOrUpdateElements(resetNewElements) { + const me = this; + const meta = me._cachedMeta; + const dataset = me.getDataset(); + let stackChanged = false; + + me._dataCheck(); + + // make sure cached _stacked status is current + meta._stacked = isStacked(meta.vScale, meta); + + // detect change in stack option + if (meta.stack !== dataset.stack) { + stackChanged = true; + // remove values from old stack + clearStacks(meta); + meta.stack = dataset.stack; + } + + // 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(resetNewElements); + + // if stack changed, update stack values for the whole dataset + if (stackChanged) { + updateStacks(me, meta._parsed); + } + } + + /** * Merges user-supplied and default dataset-level options * @private */ - configure() { - const me = this; - me._config = merge(Object.create(null), [ - defaults.controllers[me._type].datasets, - (me.chart.options.datasets || {})[me._type], - me.getDataset(), - ], { - merger(key, target, source) { - // Cloning the data is expensive and unnecessary. - // Additionally, plugins may add dataset level fields that should - // not be cloned. We identify those via an underscore prefix - if (key !== 'data' && key.charAt(0) !== '_') { - _merger(key, target, source); - } - } - }); - me._parsing = resolve([me._config.parsing, me.chart.options.parsing, true]); - } - - /** + configure() { + const me = this; + me._config = merge(Object.create(null), [ + defaults.controllers[me._type].datasets, + (me.chart.options.datasets || {})[me._type], + me.getDataset(), + ], { + merger(key, target, source) { + // Cloning the data is expensive and unnecessary. + // Additionally, plugins may add dataset level fields that should + // not be cloned. We identify those via an underscore prefix + if (key !== 'data' && key.charAt(0) !== '_') { + _merger(key, target, source); + } + } + }); + me._parsing = resolve([me._config.parsing, me.chart.options.parsing, true]); + } + + /** * @param {number} start * @param {number} count */ - parse(start, count) { - const me = this; - const {_cachedMeta: meta, _data: data} = me; - const {iScale, _stacked} = meta; - const iAxis = iScale.axis; - - let sorted = start === 0 && count === data.length ? true : meta._sorted; - let prev = start > 0 && meta._parsed[start - 1]; - let i, cur, parsed; - - if (me._parsing === false) { - meta._parsed = data; - meta._sorted = true; - } else { - if (isArray(data[start])) { - parsed = me.parseArrayData(meta, data, start, count); - } else if (isObject(data[start])) { - parsed = me.parseObjectData(meta, data, start, count); - } else { - parsed = me.parsePrimitiveData(meta, data, start, count); - } - - const isNotInOrderComparedToPrev = () => isNaN(cur[iAxis]) || (prev && cur[iAxis] < prev[iAxis]); - for (i = 0; i < count; ++i) { - meta._parsed[i + start] = cur = parsed[i]; - if (sorted) { - if (isNotInOrderComparedToPrev()) { - sorted = false; - } - prev = cur; - } - } - meta._sorted = sorted; - } - - if (_stacked) { - updateStacks(me, parsed); - } - } - - /** + parse(start, count) { + const me = this; + const {_cachedMeta: meta, _data: data} = me; + const {iScale, _stacked} = meta; + const iAxis = iScale.axis; + + let sorted = start === 0 && count === data.length ? true : meta._sorted; + let prev = start > 0 && meta._parsed[start - 1]; + let i, cur, parsed; + + if (me._parsing === false) { + meta._parsed = data; + meta._sorted = true; + } else { + if (isArray(data[start])) { + parsed = me.parseArrayData(meta, data, start, count); + } else if (isObject(data[start])) { + parsed = me.parseObjectData(meta, data, start, count); + } else { + parsed = me.parsePrimitiveData(meta, data, start, count); + } + + const isNotInOrderComparedToPrev = () => isNaN(cur[iAxis]) || (prev && cur[iAxis] < prev[iAxis]); + for (i = 0; i < count; ++i) { + meta._parsed[i + start] = cur = parsed[i]; + if (sorted) { + if (isNotInOrderComparedToPrev()) { + sorted = false; + } + prev = cur; + } + } + meta._sorted = sorted; + } + + if (_stacked) { + updateStacks(me, parsed); + } + } + + /** * Parse array of primitive values * @param {object} meta - dataset meta * @param {array} data - data array. Example [1,3,4] @@ -464,26 +464,26 @@ export default class DatasetController { * Example: {xScale0: 0, yScale0: 1} * @protected */ - parsePrimitiveData(meta, data, start, count) { - const {iScale, vScale} = meta; - const iAxis = iScale.axis; - const vAxis = vScale.axis; - const labels = iScale.getLabels(); - const singleScale = iScale === vScale; - const parsed = new Array(count); - let i, ilen, index; - - for (i = 0, ilen = count; i < ilen; ++i) { - index = i + start; - parsed[i] = { - [iAxis]: singleScale || iScale.parse(labels[index], index), - [vAxis]: vScale.parse(data[index], index) - }; - } - return parsed; - } - - /** + parsePrimitiveData(meta, data, start, count) { + const {iScale, vScale} = meta; + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const labels = iScale.getLabels(); + const singleScale = iScale === vScale; + const parsed = new Array(count); + let i, ilen, index; + + for (i = 0, ilen = count; i < ilen; ++i) { + index = i + start; + parsed[i] = { + [iAxis]: singleScale || iScale.parse(labels[index], index), + [vAxis]: vScale.parse(data[index], index) + }; + } + return parsed; + } + + /** * Parse array of arrays * @param {object} meta - dataset meta * @param {array} data - data array. Example [[1,2],[3,4]] @@ -494,23 +494,23 @@ export default class DatasetController { * Example: {x: 0, y: 1} * @protected */ - parseArrayData(meta, data, start, count) { - const {xScale, yScale} = meta; - const parsed = new Array(count); - let i, ilen, index, item; - - for (i = 0, ilen = count; i < ilen; ++i) { - index = i + start; - item = data[index]; - parsed[i] = { - x: xScale.parse(item[0], index), - y: yScale.parse(item[1], index) - }; - } - return parsed; - } - - /** + parseArrayData(meta, data, start, count) { + const {xScale, yScale} = meta; + const parsed = new Array(count); + let i, ilen, index, item; + + for (i = 0, ilen = count; i < ilen; ++i) { + index = i + start; + item = data[index]; + parsed[i] = { + x: xScale.parse(item[0], index), + y: yScale.parse(item[1], index) + }; + } + return parsed; + } + + /** * Parse array of objects * @param {object} meta - dataset meta * @param {array} data - data array. Example [{x:1, y:5}, {x:2, y:10}] @@ -521,556 +521,556 @@ export default class DatasetController { * Example: {xScale0: 0, yScale0: 1, _custom: {r: 10, foo: 'bar'}} * @protected */ - parseObjectData(meta, data, start, count) { - const {xScale, yScale} = meta; - const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; - const parsed = new Array(count); - let i, ilen, index, item; - - for (i = 0, ilen = count; i < ilen; ++i) { - index = i + start; - item = data[index]; - parsed[i] = { - x: xScale.parse(resolveObjectKey(item, xAxisKey), index), - y: yScale.parse(resolveObjectKey(item, yAxisKey), index) - }; - } - return parsed; - } - - /** + parseObjectData(meta, data, start, count) { + const {xScale, yScale} = meta; + const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; + const parsed = new Array(count); + let i, ilen, index, item; + + for (i = 0, ilen = count; i < ilen; ++i) { + index = i + start; + item = data[index]; + parsed[i] = { + x: xScale.parse(resolveObjectKey(item, xAxisKey), index), + y: yScale.parse(resolveObjectKey(item, yAxisKey), index) + }; + } + return parsed; + } + + /** * @protected */ - getParsed(index) { - return this._cachedMeta._parsed[index]; - } + getParsed(index) { + return this._cachedMeta._parsed[index]; + } - /** + /** * @protected */ - getDataElement(index) { - return this._cachedMeta.data[index]; - } + getDataElement(index) { + return this._cachedMeta.data[index]; + } - /** + /** * @protected */ - applyStack(scale, parsed) { - const chart = this.chart; - const meta = this._cachedMeta; - const value = parsed[scale.axis]; - const stack = { - keys: getSortedDatasetIndices(chart, true), - values: parsed._stacks[scale.axis] - }; - return applyStack(stack, value, meta.index); - } - - /** + applyStack(scale, parsed) { + const chart = this.chart; + const meta = this._cachedMeta; + const value = parsed[scale.axis]; + const stack = { + keys: getSortedDatasetIndices(chart, true), + values: parsed._stacks[scale.axis] + }; + return applyStack(stack, value, meta.index); + } + + /** * @protected */ - updateRangeFromParsed(range, scale, parsed, stack) { - let value = parsed[scale.axis]; - const values = stack && parsed._stacks[scale.axis]; - if (stack && values) { - stack.values = values; - // Need to consider individual stack values for data range, - // in addition to the stacked value - range.min = Math.min(range.min, value); - range.max = Math.max(range.max, value); - value = applyStack(stack, value, this._cachedMeta.index, true); - } - range.min = Math.min(range.min, value); - range.max = Math.max(range.max, value); - } - - /** + updateRangeFromParsed(range, scale, parsed, stack) { + let value = parsed[scale.axis]; + const values = stack && parsed._stacks[scale.axis]; + if (stack && values) { + stack.values = values; + // Need to consider individual stack values for data range, + // in addition to the stacked value + range.min = Math.min(range.min, value); + range.max = Math.max(range.max, value); + value = applyStack(stack, value, this._cachedMeta.index, true); + } + range.min = Math.min(range.min, value); + range.max = Math.max(range.max, value); + } + + /** * @protected */ - getMinMax(scale, canStack) { - const me = this; - const meta = me._cachedMeta; - const _parsed = meta._parsed; - const sorted = meta._sorted && scale === meta.iScale; - const ilen = _parsed.length; - const otherScale = me._getOtherScale(scale); - const stack = canStack && meta._stacked && {keys: getSortedDatasetIndices(me.chart, true), values: null}; - const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY}; - const {min: otherMin, max: otherMax} = getUserBounds(otherScale); - let i, value, parsed, otherValue; - - function _skip() { - parsed = _parsed[i]; - value = parsed[scale.axis]; - otherValue = parsed[otherScale.axis]; - return (isNaN(value) || isNaN(otherValue) || otherMin > otherValue || otherMax < otherValue); - } - - for (i = 0; i < ilen; ++i) { - if (_skip()) { - continue; - } - me.updateRangeFromParsed(range, scale, parsed, stack); - if (sorted) { - // if the data is sorted, we don't need to check further from this end of array - break; - } - } - if (sorted) { - // in the sorted case, find first non-skipped value from other end of array - for (i = ilen - 1; i >= 0; --i) { - if (_skip()) { - continue; - } - me.updateRangeFromParsed(range, scale, parsed, stack); - break; - } - } - return range; - } - - getAllParsedValues(scale) { - const parsed = this._cachedMeta._parsed; - const values = []; - let i, ilen, value; - - for (i = 0, ilen = parsed.length; i < ilen; ++i) { - value = parsed[i][scale.axis]; - if (!isNaN(value)) { - values.push(value); - } - } - return values; - } - - /** + getMinMax(scale, canStack) { + const me = this; + const meta = me._cachedMeta; + const _parsed = meta._parsed; + const sorted = meta._sorted && scale === meta.iScale; + const ilen = _parsed.length; + const otherScale = me._getOtherScale(scale); + const stack = canStack && meta._stacked && {keys: getSortedDatasetIndices(me.chart, true), values: null}; + const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY}; + const {min: otherMin, max: otherMax} = getUserBounds(otherScale); + let i, value, parsed, otherValue; + + function _skip() { + parsed = _parsed[i]; + value = parsed[scale.axis]; + otherValue = parsed[otherScale.axis]; + return (isNaN(value) || isNaN(otherValue) || otherMin > otherValue || otherMax < otherValue); + } + + for (i = 0; i < ilen; ++i) { + if (_skip()) { + continue; + } + me.updateRangeFromParsed(range, scale, parsed, stack); + if (sorted) { + // if the data is sorted, we don't need to check further from this end of array + break; + } + } + if (sorted) { + // in the sorted case, find first non-skipped value from other end of array + for (i = ilen - 1; i >= 0; --i) { + if (_skip()) { + continue; + } + me.updateRangeFromParsed(range, scale, parsed, stack); + break; + } + } + return range; + } + + getAllParsedValues(scale) { + const parsed = this._cachedMeta._parsed; + const values = []; + let i, ilen, value; + + for (i = 0, ilen = parsed.length; i < ilen; ++i) { + value = parsed[i][scale.axis]; + if (!isNaN(value)) { + values.push(value); + } + } + return values; + } + + /** * @return {number|boolean} * @protected */ - getMaxOverflow() { - return false; - } + getMaxOverflow() { + return false; + } - /** + /** * @protected */ - getLabelAndValue(index) { - const me = this; - const meta = me._cachedMeta; - const iScale = meta.iScale; - const vScale = meta.vScale; - const parsed = me.getParsed(index); - return { - label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '', - value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : '' - }; - } - - /** + getLabelAndValue(index) { + const me = this; + const meta = me._cachedMeta; + const iScale = meta.iScale; + const vScale = meta.vScale; + const parsed = me.getParsed(index); + return { + label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '', + value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : '' + }; + } + + /** * @private */ - _update(mode) { - const me = this; - const meta = me._cachedMeta; - me.configure(); - me._cachedAnimations = {}; - me._cachedDataOpts = {}; - me.update(mode || 'default'); - meta._clip = toClip(valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me.getMaxOverflow()))); - } - - /** + _update(mode) { + const me = this; + const meta = me._cachedMeta; + me.configure(); + me._cachedAnimations = {}; + me._cachedDataOpts = {}; + me.update(mode || 'default'); + meta._clip = toClip(valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me.getMaxOverflow()))); + } + + /** * @param {string} mode */ - update(mode) {} // eslint-disable-line no-unused-vars - - draw() { - const me = this; - const ctx = me._ctx; - const chart = me.chart; - const meta = me._cachedMeta; - const elements = meta.data || []; - const area = chart.chartArea; - const active = []; - const start = me._drawStart || 0; - const count = me._drawCount || (elements.length - start); - let i; - - if (meta.dataset) { - meta.dataset.draw(ctx, area, start, count); - } - - for (i = start; i < start + count; ++i) { - const element = elements[i]; - if (element.active) { - active.push(element); - } else { - element.draw(ctx, area); - } - } - - for (i = 0; i < active.length; ++i) { - active[i].draw(ctx, area); - } - } - - /** + update(mode) {} // eslint-disable-line no-unused-vars + + draw() { + const me = this; + const ctx = me._ctx; + const chart = me.chart; + const meta = me._cachedMeta; + const elements = meta.data || []; + const area = chart.chartArea; + const active = []; + const start = me._drawStart || 0; + const count = me._drawCount || (elements.length - start); + let i; + + if (meta.dataset) { + meta.dataset.draw(ctx, area, start, count); + } + + for (i = start; i < start + count; ++i) { + const element = elements[i]; + if (element.active) { + active.push(element); + } else { + element.draw(ctx, area); + } + } + + for (i = 0; i < active.length; ++i) { + active[i].draw(ctx, area); + } + } + + /** * @private */ - _addAutomaticHoverColors(index, options) { - const me = this; - const normalOptions = me.getStyle(index); - const missingColors = Object.keys(normalOptions).filter(key => key.indexOf('Color') !== -1 && !(key in options)); - let i = missingColors.length - 1; - let color; - for (; i >= 0; i--) { - color = missingColors[i]; - options[color] = getHoverColor(normalOptions[color]); - } - } - - /** + _addAutomaticHoverColors(index, options) { + const me = this; + const normalOptions = me.getStyle(index); + const missingColors = Object.keys(normalOptions).filter(key => key.indexOf('Color') !== -1 && !(key in options)); + let i = missingColors.length - 1; + let color; + for (; i >= 0; i--) { + color = missingColors[i]; + options[color] = getHoverColor(normalOptions[color]); + } + } + + /** * Returns a set of predefined style properties that should be used to represent the dataset * or the data if the index is specified * @param {number} index - data index * @param {boolean} [active] - true if hover * @return {object} style object */ - getStyle(index, active) { - const me = this; - const meta = me._cachedMeta; - const dataset = meta.dataset; - - if (!me._config) { - me.configure(); - } - - const options = dataset && index === undefined - ? me.resolveDatasetElementOptions(active) - : me.resolveDataElementOptions(index || 0, active && 'active'); - if (active) { - me._addAutomaticHoverColors(index, options); - } - - return options; - } - - /** + getStyle(index, active) { + const me = this; + const meta = me._cachedMeta; + const dataset = meta.dataset; + + if (!me._config) { + me.configure(); + } + + const options = dataset && index === undefined + ? me.resolveDatasetElementOptions(active) + : me.resolveDataElementOptions(index || 0, active && 'active'); + if (active) { + me._addAutomaticHoverColors(index, options); + } + + return options; + } + + /** * @protected */ - getContext(index, active) { - const me = this; - const dataset = me.getDataset(); - let context; - if (index >= 0 && index < me._cachedMeta.data.length) { - const element = me._cachedMeta.data[index]; - context = element.$context || + getContext(index, active) { + const me = this; + const dataset = me.getDataset(); + let context; + if (index >= 0 && index < me._cachedMeta.data.length) { + const element = me._cachedMeta.data[index]; + context = element.$context || (element.$context = createDataContext(me.getContext(), index, me.getParsed(index), dataset.data[index], element)); - } else { - context = me.$context || (me.$context = createDatasetContext(me.chart.getContext(), me.index, dataset)); - } + } else { + context = me.$context || (me.$context = createDatasetContext(me.chart.getContext(), me.index, dataset)); + } - context.active = !!active; - return context; - } + context.active = !!active; + return context; + } - /** + /** * @param {boolean} [active] * @protected */ - resolveDatasetElementOptions(active) { - return this._resolveOptions(this.datasetElementOptions, { - active, - type: this.datasetElementType.id - }); - } - - /** + resolveDatasetElementOptions(active) { + return this._resolveOptions(this.datasetElementOptions, { + active, + type: this.datasetElementType.id + }); + } + + /** * @param {number} index * @param {string} [mode] * @protected */ - resolveDataElementOptions(index, mode) { - mode = mode || 'default'; - const me = this; - const active = mode === 'active'; - const cache = me._cachedDataOpts; - const cached = cache[mode]; - const sharing = me.enableOptionSharing; - if (cached) { - return cloneIfNotShared(cached, sharing); - } - const info = {cacheable: !active}; - - const values = me._resolveOptions(me.dataElementOptions, { - index, - active, - info, - type: me.dataElementType.id - }); - - if (info.cacheable) { - // `$shared` indicates this set of options can be shared between multiple elements. - // Sharing is used to reduce number of properties to change during animation. - values.$shared = sharing; - - // We cache options by `mode`, which can be 'active' for example. This enables us - // to have the 'active' element options and 'default' options to switch between - // when interacting. - // We freeze a clone of this object, so the returned values are not frozen. - cache[mode] = Object.freeze(Object.assign({}, values)); - } - - return values; - } - - /** + resolveDataElementOptions(index, mode) { + mode = mode || 'default'; + const me = this; + const active = mode === 'active'; + const cache = me._cachedDataOpts; + const cached = cache[mode]; + const sharing = me.enableOptionSharing; + if (cached) { + return cloneIfNotShared(cached, sharing); + } + const info = {cacheable: !active}; + + const values = me._resolveOptions(me.dataElementOptions, { + index, + active, + info, + type: me.dataElementType.id + }); + + if (info.cacheable) { + // `$shared` indicates this set of options can be shared between multiple elements. + // Sharing is used to reduce number of properties to change during animation. + values.$shared = sharing; + + // We cache options by `mode`, which can be 'active' for example. This enables us + // to have the 'active' element options and 'default' options to switch between + // when interacting. + // We freeze a clone of this object, so the returned values are not frozen. + cache[mode] = Object.freeze(Object.assign({}, values)); + } + + return values; + } + + /** * @private */ - _resolveOptions(optionNames, args) { - const me = this; - const {index, active, type, info} = args; - const datasetOpts = me._config; - const options = me.chart.options.elements[type] || {}; - const values = {}; - const context = me.getContext(index, active); - const keys = optionKeys(optionNames); - - for (let i = 0, ilen = keys.length; i < ilen; ++i) { - const key = keys[i]; - const readKey = optionKey(key, active); - const value = resolve([ - datasetOpts[optionNames[readKey]], - datasetOpts[readKey], - options[readKey] - ], context, index, info); - - if (value !== undefined) { - values[key] = value; - } - } - - return values; - } - - /** + _resolveOptions(optionNames, args) { + const me = this; + const {index, active, type, info} = args; + const datasetOpts = me._config; + const options = me.chart.options.elements[type] || {}; + const values = {}; + const context = me.getContext(index, active); + const keys = optionKeys(optionNames); + + for (let i = 0, ilen = keys.length; i < ilen; ++i) { + const key = keys[i]; + const readKey = optionKey(key, active); + const value = resolve([ + datasetOpts[optionNames[readKey]], + datasetOpts[readKey], + options[readKey] + ], context, index, info); + + if (value !== undefined) { + values[key] = value; + } + } + + return values; + } + + /** * @private */ - _resolveAnimations(index, mode, active) { - const me = this; - const chart = me.chart; - const cached = me._cachedAnimations; - mode = mode || 'default'; + _resolveAnimations(index, mode, active) { + const me = this; + const chart = me.chart; + const cached = me._cachedAnimations; + mode = mode || 'default'; - if (cached[mode]) { - return cached[mode]; - } + if (cached[mode]) { + return cached[mode]; + } - const info = {cacheable: true}; - const context = me.getContext(index, active); - const chartAnim = resolve([chart.options.animation], context, index, info); - const datasetAnim = resolve([me._config.animation], context, index, info); - let config = chartAnim && mergeIf({}, [datasetAnim, chartAnim]); + const info = {cacheable: true}; + const context = me.getContext(index, active); + const chartAnim = resolve([chart.options.animation], context, index, info); + const datasetAnim = resolve([me._config.animation], context, index, info); + let config = chartAnim && mergeIf({}, [datasetAnim, chartAnim]); - if (config[mode]) { - config = Object.assign({}, config, config[mode]); - } + if (config[mode]) { + config = Object.assign({}, config, config[mode]); + } - const animations = new Animations(chart, config); + const animations = new Animations(chart, config); - if (info.cacheable) { - cached[mode] = animations && Object.freeze(animations); - } + if (info.cacheable) { + cached[mode] = animations && Object.freeze(animations); + } - return animations; - } + return animations; + } - /** + /** * Utility for getting the options object shared between elements * @protected */ - getSharedOptions(options) { - if (!options.$shared) { - return; - } - return this._sharedOptions || (this._sharedOptions = Object.assign({}, options)); - } - - /** + getSharedOptions(options) { + if (!options.$shared) { + return; + } + return this._sharedOptions || (this._sharedOptions = Object.assign({}, options)); + } + + /** * Utility for determining if `options` should be included in the updated properties * @protected */ - includeOptions(mode, sharedOptions) { - return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled; - } + includeOptions(mode, sharedOptions) { + return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled; + } - /** + /** * Utility for updating an element with new properties, using animations when appropriate. * @protected */ - updateElement(element, index, properties, mode) { - if (isDirectUpdateMode(mode)) { - Object.assign(element, properties); - } else { - this._resolveAnimations(index, mode).update(element, properties); - } - } - - /** + updateElement(element, index, properties, mode) { + if (isDirectUpdateMode(mode)) { + Object.assign(element, properties); + } else { + this._resolveAnimations(index, mode).update(element, properties); + } + } + + /** * Utility to animate the shared options, that are potentially affecting multiple elements. * @protected */ - updateSharedOptions(sharedOptions, mode, newOptions) { - if (sharedOptions) { - this._resolveAnimations(undefined, mode).update({options: sharedOptions}, {options: newOptions}); - } - } + updateSharedOptions(sharedOptions, mode, newOptions) { + if (sharedOptions) { + this._resolveAnimations(undefined, mode).update({options: sharedOptions}, {options: newOptions}); + } + } - /** + /** * @private */ - _setStyle(element, index, mode, active) { - element.active = active; - const options = this.getStyle(index, active); - this._resolveAnimations(index, mode, active).update(element, {options: this.getSharedOptions(options) || options}); - } + _setStyle(element, index, mode, active) { + element.active = active; + const options = this.getStyle(index, active); + this._resolveAnimations(index, mode, active).update(element, {options: this.getSharedOptions(options) || options}); + } - removeHoverStyle(element, datasetIndex, index) { - this._setStyle(element, index, 'active', false); - } + removeHoverStyle(element, datasetIndex, index) { + this._setStyle(element, index, 'active', false); + } - setHoverStyle(element, datasetIndex, index) { - this._setStyle(element, index, 'active', true); - } + setHoverStyle(element, datasetIndex, index) { + this._setStyle(element, index, 'active', true); + } - /** + /** * @private */ - _removeDatasetHoverStyle() { - const element = this._cachedMeta.dataset; + _removeDatasetHoverStyle() { + const element = this._cachedMeta.dataset; - if (element) { - this._setStyle(element, undefined, 'active', false); - } - } + if (element) { + this._setStyle(element, undefined, 'active', false); + } + } - /** + /** * @private */ - _setDatasetHoverStyle() { - const element = this._cachedMeta.dataset; + _setDatasetHoverStyle() { + const element = this._cachedMeta.dataset; - if (element) { - this._setStyle(element, undefined, 'active', true); - } - } + if (element) { + this._setStyle(element, undefined, 'active', true); + } + } - /** + /** * @private */ - _resyncElements(resetNewElements) { - const me = this; - const numMeta = me._cachedMeta.data.length; - const numData = me._data.length; - - if (numData > numMeta) { - me._insertElements(numMeta, numData - numMeta, resetNewElements); - } else if (numData < numMeta) { - me._removeElements(numData, numMeta - numData); - } - // Re-parse the old elements (new elements are parsed in _insertElements) - const count = Math.min(numData, numMeta); - if (count) { - me.parse(0, count); - } - } - - /** + _resyncElements(resetNewElements) { + const me = this; + const numMeta = me._cachedMeta.data.length; + const numData = me._data.length; + + if (numData > numMeta) { + me._insertElements(numMeta, numData - numMeta, resetNewElements); + } else if (numData < numMeta) { + me._removeElements(numData, numMeta - numData); + } + // Re-parse the old elements (new elements are parsed in _insertElements) + const count = Math.min(numData, numMeta); + if (count) { + me.parse(0, count); + } + } + + /** * @private */ - _insertElements(start, count, resetNewElements = true) { - const me = this; - const meta = me._cachedMeta; - const data = meta.data; - const end = start + count; - let i; - - const move = (arr) => { - arr.length += count; - for (i = arr.length - 1; i >= end; i--) { - arr[i] = arr[i - count]; - } - }; - move(data); - - for (i = start; i < end; ++i) { - data[i] = new me.dataElementType(); - } - - if (me._parsing) { - move(meta._parsed); - } - me.parse(start, count); - - if (resetNewElements) { - me.updateElements(data, start, count, 'reset'); - } - } - - updateElements(element, start, count, mode) {} // eslint-disable-line no-unused-vars - - /** + _insertElements(start, count, resetNewElements = true) { + const me = this; + const meta = me._cachedMeta; + const data = meta.data; + const end = start + count; + let i; + + const move = (arr) => { + arr.length += count; + for (i = arr.length - 1; i >= end; i--) { + arr[i] = arr[i - count]; + } + }; + move(data); + + for (i = start; i < end; ++i) { + data[i] = new me.dataElementType(); + } + + if (me._parsing) { + move(meta._parsed); + } + me.parse(start, count); + + if (resetNewElements) { + me.updateElements(data, start, count, 'reset'); + } + } + + updateElements(element, start, count, mode) {} // eslint-disable-line no-unused-vars + + /** * @private */ - _removeElements(start, count) { - const me = this; - const meta = me._cachedMeta; - if (me._parsing) { - const removed = meta._parsed.splice(start, count); - if (meta._stacked) { - clearStacks(meta, removed); - } - } - meta.data.splice(start, count); - } - - - /** + _removeElements(start, count) { + const me = this; + const meta = me._cachedMeta; + if (me._parsing) { + const removed = meta._parsed.splice(start, count); + if (meta._stacked) { + clearStacks(meta, removed); + } + } + meta.data.splice(start, count); + } + + + /** * @private */ - _onDataPush() { - const count = arguments.length; - this._insertElements(this.getDataset().data.length - count, count); - } + _onDataPush() { + const count = arguments.length; + this._insertElements(this.getDataset().data.length - count, count); + } - /** + /** * @private */ - _onDataPop() { - this._removeElements(this._cachedMeta.data.length - 1, 1); - } + _onDataPop() { + this._removeElements(this._cachedMeta.data.length - 1, 1); + } - /** + /** * @private */ - _onDataShift() { - this._removeElements(0, 1); - } + _onDataShift() { + this._removeElements(0, 1); + } - /** + /** * @private */ - _onDataSplice(start, count) { - this._removeElements(start, count); - this._insertElements(start, arguments.length - 2); - } + _onDataSplice(start, count) { + this._removeElements(start, count); + this._insertElements(start, arguments.length - 2); + } - /** + /** * @private */ - _onDataUnshift() { - this._insertElements(0, arguments.length); - } + _onDataUnshift() { + this._insertElements(0, arguments.length); + } } /** @@ -1095,13 +1095,13 @@ DatasetController.prototype.dataElementType = null; * @type {string[]} */ DatasetController.prototype.datasetElementOptions = [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth' + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' ]; /** @@ -1111,8 +1111,8 @@ DatasetController.prototype.datasetElementOptions = [ * @type {string[]|object} */ DatasetController.prototype.dataElementOptions = [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'pointStyle' + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' ]; diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js index 8e0a4277360..14b16e1c4f0 100644 --- a/src/core/core.defaults.js +++ b/src/core/core.defaults.js @@ -6,15 +6,15 @@ import {isObject, merge, valueOrDefault} from '../helpers/helpers.core'; * @return {object} */ function getScope(node, key) { - if (!key) { - return node; - } - const keys = key.split('.'); - for (let i = 0, n = keys.length; i < n; ++i) { - const k = keys[i]; - node = node[k] || (node[k] = Object.create(null)); - } - return node; + if (!key) { + return node; + } + const keys = key.split('.'); + for (let i = 0, n = keys.length; i < n; ++i) { + const k = keys[i]; + node = node[k] || (node[k] = Object.create(null)); + } + return node; } /** @@ -22,62 +22,62 @@ function getScope(node, key) { * Note: class is exported for typedoc */ export class Defaults { - constructor() { - this.backgroundColor = 'rgba(0,0,0,0.1)'; - this.borderColor = 'rgba(0,0,0,0.1)'; - this.color = '#666'; - this.controllers = {}; - this.elements = {}; - this.events = [ - 'mousemove', - 'mouseout', - 'click', - 'touchstart', - 'touchmove' - ]; - this.font = { - family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - size: 12, - style: 'normal', - lineHeight: 1.2, - weight: null - }; - this.hover = { - onHover: null - }; - this.interaction = { - mode: 'nearest', - intersect: true - }; - this.maintainAspectRatio = true; - this.onHover = null; - this.onClick = null; - this.plugins = {}; - this.responsive = true; - this.scale = undefined; - this.scales = {}; - this.showLine = true; - } + constructor() { + this.backgroundColor = 'rgba(0,0,0,0.1)'; + this.borderColor = 'rgba(0,0,0,0.1)'; + this.color = '#666'; + this.controllers = {}; + this.elements = {}; + this.events = [ + 'mousemove', + 'mouseout', + 'click', + 'touchstart', + 'touchmove' + ]; + this.font = { + family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + size: 12, + style: 'normal', + lineHeight: 1.2, + weight: null + }; + this.hover = { + onHover: null + }; + this.interaction = { + mode: 'nearest', + intersect: true + }; + this.maintainAspectRatio = true; + this.onHover = null; + this.onClick = null; + this.plugins = {}; + this.responsive = true; + this.scale = undefined; + this.scales = {}; + this.showLine = true; + } - /** + /** * @param {string|object} scope * @param {object} [values] */ - set(scope, values) { - if (typeof scope === 'string') { - return merge(getScope(this, scope), values); - } - return merge(getScope(this, ''), scope); - } + set(scope, values) { + if (typeof scope === 'string') { + return merge(getScope(this, scope), values); + } + return merge(getScope(this, ''), scope); + } - /** + /** * @param {string} scope */ - get(scope) { - return getScope(this, scope); - } + get(scope) { + return getScope(this, scope); + } - /** + /** * Routes the named defaults to fallback to another scope/name. * This routing is useful when those target values, like defaults.color, are changed runtime. * If the values would be copied, the runtime change would not take effect. By routing, the @@ -94,34 +94,34 @@ export class Defaults { * Empty string ('') is the root of defaults. * @param {string} targetName The target name in the target scope the property should be routed to. */ - route(scope, name, targetScope, targetName) { - const scopeObject = getScope(this, scope); - const targetScopeObject = getScope(this, targetScope); - const privateName = '_' + name; + route(scope, name, targetScope, targetName) { + const scopeObject = getScope(this, scope); + const targetScopeObject = getScope(this, targetScope); + const privateName = '_' + name; - Object.defineProperties(scopeObject, { - // A private property is defined to hold the actual value, when this property is set in its scope (set in the setter) - [privateName]: { - value: scopeObject[name], - writable: true - }, - // The actual property is defined as getter/setter so we can do the routing when value is not locally set. - [name]: { - enumerable: true, - get() { - const local = this[privateName]; - const target = targetScopeObject[targetName]; - if (isObject(local)) { - return Object.assign({}, target, local); - } - return valueOrDefault(local, target); - }, - set(value) { - this[privateName] = value; - } - } - }); - } + Object.defineProperties(scopeObject, { + // A private property is defined to hold the actual value, when this property is set in its scope (set in the setter) + [privateName]: { + value: scopeObject[name], + writable: true + }, + // The actual property is defined as getter/setter so we can do the routing when value is not locally set. + [name]: { + enumerable: true, + get() { + const local = this[privateName]; + const target = targetScopeObject[targetName]; + if (isObject(local)) { + return Object.assign({}, target, local); + } + return valueOrDefault(local, target); + }, + set(value) { + this[privateName] = value; + } + } + }); + } } // singleton instance diff --git a/src/core/core.element.js b/src/core/core.element.js index aefa1d918d4..dc119dfcda3 100644 --- a/src/core/core.element.js +++ b/src/core/core.element.js @@ -2,45 +2,45 @@ import {isNumber} from '../helpers/helpers.math'; export default class Element { - constructor() { - this.x = undefined; - this.y = undefined; - this.active = false; - this.options = undefined; - this.$animations = undefined; - } + constructor() { + this.x = undefined; + this.y = undefined; + this.active = false; + this.options = undefined; + this.$animations = undefined; + } - /** + /** * @param {boolean} [useFinalPosition] */ - tooltipPosition(useFinalPosition) { - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return {x, y}; - } + tooltipPosition(useFinalPosition) { + const {x, y} = this.getProps(['x', 'y'], useFinalPosition); + return {x, y}; + } - hasValue() { - return isNumber(this.x) && isNumber(this.y); - } + hasValue() { + return isNumber(this.x) && isNumber(this.y); + } - /** + /** * Gets the current or final value of each prop. Can return extra properties (whole object). * @param {string[]} props - properties to get * @param {boolean} [final] - get the final value (animation target) * @return {object} */ - getProps(props, final) { - const me = this; - const anims = this.$animations; - if (!final || !anims) { - // let's not create an object, if not needed - return me; - } - const ret = {}; - props.forEach(prop => { - ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : me[prop]; - }); - return ret; - } + getProps(props, final) { + const me = this; + const anims = this.$animations; + if (!final || !anims) { + // let's not create an object, if not needed + return me; + } + const ret = {}; + props.forEach(prop => { + ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : me[prop]; + }); + return ret; + } } /** diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index abcd5ca67f5..10db9f487de 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -16,14 +16,14 @@ import {getRelativePosition as helpersGetRelativePosition} from '../helpers/help * @returns {object} the event position */ function getRelativePosition(e, chart) { - if ('native' in e) { - return { - x: e.x, - y: e.y - }; - } - - return helpersGetRelativePosition(e, chart); + if ('native' in e) { + return { + x: e.x, + y: e.y + }; + } + + return helpersGetRelativePosition(e, chart); } /** @@ -32,18 +32,18 @@ function getRelativePosition(e, chart) { * @param {function} handler - the callback to execute for each visible item */ function evaluateAllVisibleItems(chart, handler) { - const metasets = chart.getSortedVisibleDatasetMetas(); - let index, data, element; - - for (let i = 0, ilen = metasets.length; i < ilen; ++i) { - ({index, data} = metasets[i]); - for (let j = 0, jlen = data.length; j < jlen; ++j) { - element = data[j]; - if (!element.skip) { - handler(element, index, j); - } - } - } + const metasets = chart.getSortedVisibleDatasetMetas(); + let index, data, element; + + for (let i = 0, ilen = metasets.length; i < ilen; ++i) { + ({index, data} = metasets[i]); + for (let j = 0, jlen = data.length; j < jlen; ++j) { + element = data[j]; + if (!element.skip) { + handler(element, index, j); + } + } + } } /** @@ -55,27 +55,27 @@ function evaluateAllVisibleItems(chart, handler) { * @returns {{lo:number, hi:number}} indices to search data array between */ function binarySearch(metaset, axis, value, intersect) { - const {controller, data, _sorted} = metaset; - const iScale = controller._cachedMeta.iScale; - if (iScale && axis === iScale.axis && _sorted && data.length) { - const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey; - if (!intersect) { - return lookupMethod(data, axis, value); - } else if (controller._sharedOptions) { - // _sharedOptions indicates that each element has equal options -> equal proportions - // So we can do a ranged binary search based on the range of first element and - // be confident to get the full range of indices that can intersect with the value. - const el = data[0]; - const range = typeof el.getRange === 'function' && el.getRange(axis); - if (range) { - const start = lookupMethod(data, axis, value - range); - const end = lookupMethod(data, axis, value + range); - return {lo: start.lo, hi: end.hi}; - } - } - } - // Default to all elements, when binary search can not be used. - return {lo: 0, hi: data.length - 1}; + const {controller, data, _sorted} = metaset; + const iScale = controller._cachedMeta.iScale; + if (iScale && axis === iScale.axis && _sorted && data.length) { + const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey; + if (!intersect) { + return lookupMethod(data, axis, value); + } else if (controller._sharedOptions) { + // _sharedOptions indicates that each element has equal options -> equal proportions + // So we can do a ranged binary search based on the range of first element and + // be confident to get the full range of indices that can intersect with the value. + const el = data[0]; + const range = typeof el.getRange === 'function' && el.getRange(axis); + if (range) { + const start = lookupMethod(data, axis, value - range); + const end = lookupMethod(data, axis, value + range); + return {lo: start.lo, hi: end.hi}; + } + } + } + // Default to all elements, when binary search can not be used. + return {lo: 0, hi: data.length - 1}; } /** @@ -87,18 +87,18 @@ function binarySearch(metaset, axis, value, intersect) { * @param {boolean} [intersect] - consider intersecting items */ function optimizedEvaluateItems(chart, axis, position, handler, intersect) { - const metasets = chart.getSortedVisibleDatasetMetas(); - const value = position[axis]; - for (let i = 0, ilen = metasets.length; i < ilen; ++i) { - const {index, data} = metasets[i]; - const {lo, hi} = binarySearch(metasets[i], axis, value, intersect); - for (let j = lo; j <= hi; ++j) { - const element = data[j]; - if (!element.skip) { - handler(element, index, j); - } - } - } + const metasets = chart.getSortedVisibleDatasetMetas(); + const value = position[axis]; + for (let i = 0, ilen = metasets.length; i < ilen; ++i) { + const {index, data} = metasets[i]; + const {lo, hi} = binarySearch(metasets[i], axis, value, intersect); + for (let j = lo; j <= hi; ++j) { + const element = data[j]; + if (!element.skip) { + handler(element, index, j); + } + } + } } /** @@ -107,14 +107,14 @@ function optimizedEvaluateItems(chart, axis, position, handler, intersect) { * @param {string} axis - the axis mode. x|y|xy */ function getDistanceMetricForAxis(axis) { - const useX = axis.indexOf('x') !== -1; - const useY = axis.indexOf('y') !== -1; - - return function(pt1, pt2) { - const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; - const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; - return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); - }; + const useX = axis.indexOf('x') !== -1; + const useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; } /** @@ -126,20 +126,20 @@ function getDistanceMetricForAxis(axis) { * @return {InteractionItem[]} the nearest items */ function getIntersectItems(chart, position, axis, useFinalPosition) { - const items = []; + const items = []; - if (!_isPointInArea(position, chart.chartArea)) { - return items; - } + if (!_isPointInArea(position, chart.chartArea)) { + return items; + } - const evaluationFunc = function(element, datasetIndex, index) { - if (element.inRange(position.x, position.y, useFinalPosition)) { - items.push({element, datasetIndex, index}); - } - }; + const evaluationFunc = function(element, datasetIndex, index) { + if (element.inRange(position.x, position.y, useFinalPosition)) { + items.push({element, datasetIndex, index}); + } + }; - optimizedEvaluateItems(chart, axis, position, evaluationFunc, true); - return items; + optimizedEvaluateItems(chart, axis, position, evaluationFunc, true); + return items; } /** @@ -152,57 +152,57 @@ function getIntersectItems(chart, position, axis, useFinalPosition) { * @return {InteractionItem[]} the nearest items */ function getNearestItems(chart, position, axis, intersect, useFinalPosition) { - const distanceMetric = getDistanceMetricForAxis(axis); - let minDistance = Number.POSITIVE_INFINITY; - let items = []; - - if (!_isPointInArea(position, chart.chartArea)) { - return items; - } - - const evaluationFunc = function(element, datasetIndex, index) { - if (intersect && !element.inRange(position.x, position.y, useFinalPosition)) { - return; - } - - const center = element.getCenterPoint(useFinalPosition); - const distance = distanceMetric(position, center); - if (distance < minDistance) { - items = [{element, datasetIndex, index}]; - minDistance = distance; - } else if (distance === minDistance) { - // Can have multiple items at the same distance in which case we sort by size - items.push({element, datasetIndex, index}); - } - }; - - optimizedEvaluateItems(chart, axis, position, evaluationFunc); - return items; + const distanceMetric = getDistanceMetricForAxis(axis); + let minDistance = Number.POSITIVE_INFINITY; + let items = []; + + if (!_isPointInArea(position, chart.chartArea)) { + return items; + } + + const evaluationFunc = function(element, datasetIndex, index) { + if (intersect && !element.inRange(position.x, position.y, useFinalPosition)) { + return; + } + + const center = element.getCenterPoint(useFinalPosition); + const distance = distanceMetric(position, center); + if (distance < minDistance) { + items = [{element, datasetIndex, index}]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + items.push({element, datasetIndex, index}); + } + }; + + optimizedEvaluateItems(chart, axis, position, evaluationFunc); + return items; } function getAxisItems(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); - const items = []; - const axis = options.axis; - const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; - let intersectsItem = false; - - evaluateAllVisibleItems(chart, (element, datasetIndex, index) => { - if (element[rangeMethod](position[axis], useFinalPosition)) { - items.push({element, datasetIndex, index}); - } - - if (element.inRange(position.x, position.y, useFinalPosition)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - return []; - } - return items; + const position = getRelativePosition(e, chart); + const items = []; + const axis = options.axis; + const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; + let intersectsItem = false; + + evaluateAllVisibleItems(chart, (element, datasetIndex, index) => { + if (element[rangeMethod](position[axis], useFinalPosition)) { + items.push({element, datasetIndex, index}); + } + + if (element.inRange(position.x, position.y, useFinalPosition)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + return []; + } + return items; } /** @@ -210,9 +210,9 @@ function getAxisItems(chart, e, options, useFinalPosition) { * @namespace Chart.Interaction */ export default { - // Helper function for different modes - modes: { - /** + // Helper function for different modes + modes: { + /** * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item * @function Chart.Interaction.modes.index @@ -223,33 +223,33 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - index(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); - // Default axis for index mode is 'x' to match old behaviour - const axis = options.axis || 'x'; - const items = options.intersect - ? getIntersectItems(chart, position, axis, useFinalPosition) - : getNearestItems(chart, position, axis, false, useFinalPosition); - const elements = []; - - if (!items.length) { - return []; - } - - chart.getSortedVisibleDatasetMetas().forEach((meta) => { - const index = items[0].index; - const element = meta.data[index]; - - // don't count items that are skipped (null data) - if (element && !element.skip) { - elements.push({element, datasetIndex: meta.index, index}); - } - }); - - return elements; - }, - - /** + index(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + const axis = options.axis || 'x'; + const items = options.intersect + ? getIntersectItems(chart, position, axis, useFinalPosition) + : getNearestItems(chart, position, axis, false, useFinalPosition); + const elements = []; + + if (!items.length) { + return []; + } + + chart.getSortedVisibleDatasetMetas().forEach((meta) => { + const index = items[0].index; + const element = meta.data[index]; + + // don't count items that are skipped (null data) + if (element && !element.skip) { + elements.push({element, datasetIndex: meta.index, index}); + } + }); + + return elements; + }, + + /** * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something * If the options.intersect is false, we find the nearest item and return the items in that dataset * @function Chart.Interaction.modes.dataset @@ -259,26 +259,26 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - dataset(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); - const axis = options.axis || 'xy'; - let items = options.intersect - ? getIntersectItems(chart, position, axis, useFinalPosition) : - getNearestItems(chart, position, axis, false, useFinalPosition); - - if (items.length > 0) { - const datasetIndex = items[0].datasetIndex; - const data = chart.getDatasetMeta(datasetIndex).data; - items = []; - for (let i = 0; i < data.length; ++i) { - items.push({element: data[i], datasetIndex, index: i}); - } - } - - return items; - }, - - /** + dataset(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + let items = options.intersect + ? getIntersectItems(chart, position, axis, useFinalPosition) : + getNearestItems(chart, position, axis, false, useFinalPosition); + + if (items.length > 0) { + const datasetIndex = items[0].datasetIndex; + const data = chart.getDatasetMeta(datasetIndex).data; + items = []; + for (let i = 0; i < data.length; ++i) { + items.push({element: data[i], datasetIndex, index: i}); + } + } + + return items; + }, + + /** * Point mode returns all elements that hit test based on the event position * of the event * @function Chart.Interaction.modes.intersect @@ -288,13 +288,13 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - point(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); - const axis = options.axis || 'xy'; - return getIntersectItems(chart, position, axis, useFinalPosition); - }, + point(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + return getIntersectItems(chart, position, axis, useFinalPosition); + }, - /** + /** * nearest mode returns the element closest to the point * @function Chart.Interaction.modes.intersect * @param {Chart} chart - the chart we are returning items from @@ -303,13 +303,13 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - nearest(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); - const axis = options.axis || 'xy'; - return getNearestItems(chart, position, axis, options.intersect, useFinalPosition); - }, + nearest(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + return getNearestItems(chart, position, axis, options.intersect, useFinalPosition); + }, - /** + /** * x mode returns the elements that hit-test at the current x coordinate * @function Chart.Interaction.modes.x * @param {Chart} chart - the chart we are returning items from @@ -318,12 +318,12 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - x(chart, e, options, useFinalPosition) { - options.axis = 'x'; - return getAxisItems(chart, e, options, useFinalPosition); - }, + x(chart, e, options, useFinalPosition) { + options.axis = 'x'; + return getAxisItems(chart, e, options, useFinalPosition); + }, - /** + /** * y mode returns the elements that hit-test at the current y coordinate * @function Chart.Interaction.modes.y * @param {Chart} chart - the chart we are returning items from @@ -332,9 +332,9 @@ export default { * @param {boolean} [useFinalPosition] - use final element position (animation target) * @return {InteractionItem[]} - items that are found */ - y(chart, e, options, useFinalPosition) { - options.axis = 'y'; - return getAxisItems(chart, e, options, useFinalPosition); - } - } + y(chart, e, options, useFinalPosition) { + options.axis = 'y'; + return getAxisItems(chart, e, options, useFinalPosition); + } + } }; diff --git a/src/core/core.intl.js b/src/core/core.intl.js index 88803cda287..176e346be94 100644 --- a/src/core/core.intl.js +++ b/src/core/core.intl.js @@ -2,16 +2,16 @@ const intlCache = new Map(); export function getNumberFormat(locale, options) { - options = options || {}; - const cacheKey = locale + JSON.stringify(options); - let formatter = intlCache.get(cacheKey); - if (!formatter) { - formatter = new Intl.NumberFormat(locale, options); - intlCache.set(cacheKey, formatter); - } - return formatter; + options = options || {}; + const cacheKey = locale + JSON.stringify(options); + let formatter = intlCache.get(cacheKey); + if (!formatter) { + formatter = new Intl.NumberFormat(locale, options); + intlCache.set(cacheKey, formatter); + } + return formatter; } export function formatNumber(num, locale, options) { - return getNumberFormat(locale, options).format(num); + return getNumberFormat(locale, options).format(num); } diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index baf1637baaf..31a4cb618a1 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -9,208 +9,208 @@ import {toPadding, resolve} from '../helpers/helpers.options'; const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom']; function filterByPosition(array, position) { - return array.filter(v => v.pos === position); + return array.filter(v => v.pos === position); } function filterDynamicPositionByAxis(array, axis) { - return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis); + return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis); } function sortByWeight(array, reverse) { - return array.sort((a, b) => { - const v0 = reverse ? b : a; - const v1 = reverse ? a : b; - return v0.weight === v1.weight ? - v0.index - v1.index : - v0.weight - v1.weight; - }); + return array.sort((a, b) => { + const v0 = reverse ? b : a; + const v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); } function wrapBoxes(boxes) { - const layoutBoxes = []; - let i, ilen, box; - - for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { - box = boxes[i]; - layoutBoxes.push({ - index: i, - box, - pos: box.position, - horizontal: box.isHorizontal(), - weight: box.weight - }); - } - return layoutBoxes; + const layoutBoxes = []; + let i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; } function setLayoutDims(layouts, params) { - let i, ilen, layout; - for (i = 0, ilen = layouts.length; i < ilen; ++i) { - layout = layouts[i]; - // store dimensions used instead of available chartArea in fitBoxes - if (layout.horizontal) { - layout.width = layout.box.fullSize && params.availableWidth; - layout.height = params.hBoxMaxHeight; - } else { - layout.width = params.vBoxMaxWidth; - layout.height = layout.box.fullSize && params.availableHeight; - } - } + let i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store dimensions used instead of available chartArea in fitBoxes + if (layout.horizontal) { + layout.width = layout.box.fullSize && params.availableWidth; + layout.height = params.hBoxMaxHeight; + } else { + layout.width = params.vBoxMaxWidth; + layout.height = layout.box.fullSize && params.availableHeight; + } + } } function buildLayoutBoxes(boxes) { - const layoutBoxes = wrapBoxes(boxes); - const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); - const right = sortByWeight(filterByPosition(layoutBoxes, 'right')); - const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); - const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); - const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x'); - const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y'); - - return { - leftAndTop: left.concat(top), - rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), - chartArea: filterByPosition(layoutBoxes, 'chartArea'), - vertical: left.concat(right).concat(centerVertical), - horizontal: top.concat(bottom).concat(centerHorizontal) - }; + const layoutBoxes = wrapBoxes(boxes); + const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + const right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x'); + const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y'); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right).concat(centerVertical), + horizontal: top.concat(bottom).concat(centerHorizontal) + }; } function getCombinedMax(maxPadding, chartArea, a, b) { - return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); } function updateDims(chartArea, params, layout) { - const box = layout.box; - const maxPadding = chartArea.maxPadding; - - if (isObject(layout.pos)) { - // dynamically placed boxes are not considered - return; - } - if (layout.size) { - // this layout was already counted for, lets first reduce old size - chartArea[layout.pos] -= layout.size; - } - layout.size = layout.horizontal ? Math.min(layout.height, box.height) : Math.min(layout.width, box.width); - chartArea[layout.pos] += layout.size; - - if (box.getPadding) { - const boxPadding = box.getPadding(); - maxPadding.top = Math.max(maxPadding.top, boxPadding.top); - maxPadding.left = Math.max(maxPadding.left, boxPadding.left); - maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); - maxPadding.right = Math.max(maxPadding.right, boxPadding.right); - } - - const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right')); - const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom')); - - if (newWidth !== chartArea.w || newHeight !== chartArea.h) { - chartArea.w = newWidth; - chartArea.h = newHeight; - - // return true if chart area changed in layout's direction - return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; - } + const box = layout.box; + const maxPadding = chartArea.maxPadding; + + if (isObject(layout.pos)) { + // dynamically placed boxes are not considered + return; + } + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? Math.min(layout.height, box.height) : Math.min(layout.width, box.width); + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + const boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right')); + const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom')); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } } function handleMaxPadding(chartArea) { - const maxPadding = chartArea.maxPadding; - - function updatePos(pos) { - const change = Math.max(maxPadding[pos] - chartArea[pos], 0); - chartArea[pos] += change; - return change; - } - chartArea.y += updatePos('top'); - chartArea.x += updatePos('left'); - updatePos('right'); - updatePos('bottom'); + const maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + const change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); } function getMargins(horizontal, chartArea) { - const maxPadding = chartArea.maxPadding; - - function marginForPositions(positions) { - const margin = {left: 0, top: 0, right: 0, bottom: 0}; - positions.forEach((pos) => { - margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); - }); - return margin; - } - - return horizontal - ? marginForPositions(['left', 'right']) - : marginForPositions(['top', 'bottom']); + const maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + const margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach((pos) => { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); } function fitBoxes(boxes, chartArea, params) { - const refitBoxes = []; - let i, ilen, layout, box, refit, changed; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - - box.update( - layout.width || chartArea.w, - layout.height || chartArea.h, - getMargins(layout.horizontal, chartArea) - ); - if (updateDims(chartArea, params, layout)) { - changed = true; - if (refitBoxes.length) { - // Dimensions changed and there were non full width boxes before this - // -> we have to refit those - refit = true; - } - } - if (!box.fullSize) { // fullSize boxes don't need to be re-fitted in any case - refitBoxes.push(layout); - } - } - - return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; + const refitBoxes = []; + let i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullSize) { // fullSize boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; } function placeBoxes(boxes, chartArea, params) { - const userPadding = params.padding; - let x = chartArea.x; - let y = chartArea.y; - let i, ilen, layout, box; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - if (layout.horizontal) { - box.left = box.fullSize ? userPadding.left : chartArea.left; - box.right = box.fullSize ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; - box.top = y; - box.bottom = y + box.height; - box.width = box.right - box.left; - y = box.bottom; - } else { - box.left = x; - box.right = x + box.width; - box.top = box.fullSize ? userPadding.top : chartArea.top; - box.bottom = box.fullSize ? params.outerHeight - userPadding.right : chartArea.top + chartArea.h; - box.height = box.bottom - box.top; - x = box.right; - } - } - - chartArea.x = x; - chartArea.y = y; + const userPadding = params.padding; + let x = chartArea.x; + let y = chartArea.y; + let i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullSize ? userPadding.left : chartArea.left; + box.right = box.fullSize ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = box.fullSize ? userPadding.top : chartArea.top; + box.bottom = box.fullSize ? params.outerHeight - userPadding.right : chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; } defaults.set('layout', { - padding: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } }); /** @@ -237,168 +237,168 @@ defaults.set('layout', { // It is this service's responsibility of carrying out that layout. export default { - /** + /** * Register a box to a chart. * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. * @param {Chart} chart - the chart to use * @param {LayoutItem} item - the item to add to be laid out */ - addBox(chart, item) { - if (!chart.boxes) { - chart.boxes = []; - } - - // initialize item with default values - item.fullSize = item.fullSize || false; - item.position = item.position || 'top'; - item.weight = item.weight || 0; - // @ts-ignore - item._layers = item._layers || function() { - return [{ - z: 0, - draw(chartArea) { - item.draw(chartArea); - } - }]; - }; - - chart.boxes.push(item); - }, - - /** + addBox(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullSize = item.fullSize || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + // @ts-ignore + item._layers = item._layers || function() { + return [{ + z: 0, + draw(chartArea) { + item.draw(chartArea); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** * Remove a layoutItem from a chart * @param {Chart} chart - the chart to remove the box from * @param {LayoutItem} layoutItem - the item to remove from the layout */ - removeBox(chart, layoutItem) { - const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; - if (index !== -1) { - chart.boxes.splice(index, 1); - } - }, - - /** + removeBox(chart, layoutItem) { + const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** * Sets (or updates) options on the given `item`. * @param {Chart} chart - the chart in which the item lives (or will be added to) * @param {LayoutItem} item - the item to configure with the given options * @param {object} options - the new item options. */ - configure(chart, item, options) { - item.fullSize = options.fullSize; - item.position = options.position; - item.weight = options.weight; - }, + configure(chart, item, options) { + item.fullSize = options.fullSize; + item.position = options.position; + item.weight = options.weight; + }, - /** + /** * Fits boxes of the given chart into the given size by having each box measure itself * then running a fitting algorithm * @param {Chart} chart - the chart * @param {number} width - the width to fit into * @param {number} height - the height to fit into */ - update(chart, width, height) { - if (!chart) { - return; - } - - const layoutOptions = chart.options.layout || {}; - const context = {chart}; - const padding = toPadding(resolve([layoutOptions.padding], context)); - - const availableWidth = width - padding.width; - const availableHeight = height - padding.height; - const boxes = buildLayoutBoxes(chart.boxes); - const verticalBoxes = boxes.vertical; - const horizontalBoxes = boxes.horizontal; - - // Before any changes are made, notify boxes that an update is about to being - // This is used to clear any cached data (e.g. scale limits) - each(chart.boxes, box => { - if (typeof box.beforeLayout === 'function') { - box.beforeLayout(); - } - }); - - // Essentially we now have any number of boxes on each of the 4 sides. - // Our canvas looks like the following. - // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and - // B1 is the bottom axis - // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays - // These locations are single-box locations only, when trying to register a chartArea location that is already taken, - // an error will be thrown. - // - // |----------------------------------------------------| - // | T1 (Full Width) | - // |----------------------------------------------------| - // | | | T2 | | - // | |----|-------------------------------------|----| - // | | | C1 | | C2 | | - // | | |----| |----| | - // | | | | | - // | L1 | L2 | ChartArea (C0) | R1 | - // | | | | | - // | | |----| |----| | - // | | | C3 | | C4 | | - // | |----|-------------------------------------|----| - // | | | B1 | | - // |----------------------------------------------------| - // | B2 (Full Width) | - // |----------------------------------------------------| - // - - const params = Object.freeze({ - outerWidth: width, - outerHeight: height, - padding, - availableWidth, - availableHeight, - vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, - hBoxMaxHeight: availableHeight / 2 - }); - const chartArea = Object.assign({ - maxPadding: Object.assign({}, padding), - w: availableWidth, - h: availableHeight, - x: padding.left, - y: padding.top - }, padding); - - setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); - - // First fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - - // Then fit horizontal boxes - if (fitBoxes(horizontalBoxes, chartArea, params)) { - // if the area changed, re-fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - } - - handleMaxPadding(chartArea); - - // Finally place the boxes to correct coordinates - placeBoxes(boxes.leftAndTop, chartArea, params); - - // Move to opposite side of chart - chartArea.x += chartArea.w; - chartArea.y += chartArea.h; - - placeBoxes(boxes.rightAndBottom, chartArea, params); - - chart.chartArea = { - left: chartArea.left, - top: chartArea.top, - right: chartArea.left + chartArea.w, - bottom: chartArea.top + chartArea.h, - height: chartArea.h, - width: chartArea.w, - }; - - // Finally update boxes in chartArea (radial scale for example) - each(boxes.chartArea, (layout) => { - const box = layout.box; - Object.assign(box, chart.chartArea); - box.update(chartArea.w, chartArea.h); - }); - } + update(chart, width, height) { + if (!chart) { + return; + } + + const layoutOptions = chart.options.layout || {}; + const context = {chart}; + const padding = toPadding(resolve([layoutOptions.padding], context)); + + const availableWidth = width - padding.width; + const availableHeight = height - padding.height; + const boxes = buildLayoutBoxes(chart.boxes); + const verticalBoxes = boxes.vertical; + const horizontalBoxes = boxes.horizontal; + + // Before any changes are made, notify boxes that an update is about to being + // This is used to clear any cached data (e.g. scale limits) + each(chart.boxes, box => { + if (typeof box.beforeLayout === 'function') { + box.beforeLayout(); + } + }); + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + const params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding, + availableWidth, + availableHeight, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + const chartArea = Object.assign({ + maxPadding: Object.assign({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h, + height: chartArea.h, + width: chartArea.w, + }; + + // Finally update boxes in chartArea (radial scale for example) + each(boxes.chartArea, (layout) => { + const box = layout.box; + Object.assign(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } }; diff --git a/src/core/core.plugins.js b/src/core/core.plugins.js index 5ebd25fa043..42f962c5b47 100644 --- a/src/core/core.plugins.js +++ b/src/core/core.plugins.js @@ -10,11 +10,11 @@ import {callback as callCallback, mergeIf, valueOrDefault} from '../helpers/help */ export default class PluginService { - constructor() { - this._init = []; - } + constructor() { + this._init = []; + } - /** + /** * Calls enabled plugins for `chart` on the specified hook and with the given args. * This method immediately returns as soon as a plugin explicitly returns false. The * returned value can be used, for instance, to interrupt the current action. @@ -23,137 +23,137 @@ export default class PluginService { * @param {object} [args] - Extra arguments to apply to the hook call. * @returns {boolean} false if any of the plugins return false, else returns true. */ - notify(chart, hook, args) { - const me = this; + notify(chart, hook, args) { + const me = this; - if (hook === 'beforeInit') { - me._init = me._createDescriptors(chart, true); - me._notify(me._init, chart, 'install'); - } + if (hook === 'beforeInit') { + me._init = me._createDescriptors(chart, true); + me._notify(me._init, chart, 'install'); + } - const descriptors = me._descriptors(chart); - const result = me._notify(descriptors, chart, hook, args); + const descriptors = me._descriptors(chart); + const result = me._notify(descriptors, chart, hook, args); - if (hook === 'destroy') { - me._notify(descriptors, chart, 'stop'); - me._notify(me._init, chart, 'uninstall'); - } - return result; - } + if (hook === 'destroy') { + me._notify(descriptors, chart, 'stop'); + me._notify(me._init, chart, 'uninstall'); + } + return result; + } - /** + /** * @private */ - _notify(descriptors, chart, hook, args) { - args = args || {}; - for (const descriptor of descriptors) { - const plugin = descriptor.plugin; - const method = plugin[hook]; - const params = [chart, args, descriptor.options]; - if (callCallback(method, params, plugin) === false && args.cancelable) { - return false; - } - } - - return true; - } - - invalidate() { - // When plugins are registered, there is the possibility of a double - // invalidate situation. In this case, we only want to invalidate once. - // If we invalidate multiple times, the `_oldCache` is lost and all of the - // plugins are restarted without being correctly stopped. - // See https://github.com/chartjs/Chart.js/issues/8147 - if (!isNullOrUndef(this._cache)) { - this._oldCache = this._cache; - this._cache = undefined; - } - } - - /** + _notify(descriptors, chart, hook, args) { + args = args || {}; + for (const descriptor of descriptors) { + const plugin = descriptor.plugin; + const method = plugin[hook]; + const params = [chart, args, descriptor.options]; + if (callCallback(method, params, plugin) === false && args.cancelable) { + return false; + } + } + + return true; + } + + invalidate() { + // When plugins are registered, there is the possibility of a double + // invalidate situation. In this case, we only want to invalidate once. + // If we invalidate multiple times, the `_oldCache` is lost and all of the + // plugins are restarted without being correctly stopped. + // See https://github.com/chartjs/Chart.js/issues/8147 + if (!isNullOrUndef(this._cache)) { + this._oldCache = this._cache; + this._cache = undefined; + } + } + + /** * @param {Chart} chart * @private */ - _descriptors(chart) { - if (this._cache) { - return this._cache; - } + _descriptors(chart) { + if (this._cache) { + return this._cache; + } - const descriptors = this._cache = this._createDescriptors(chart); + const descriptors = this._cache = this._createDescriptors(chart); - this._notifyStateChanges(chart); + this._notifyStateChanges(chart); - return descriptors; - } + return descriptors; + } - _createDescriptors(chart, all) { - const config = chart && chart.config; - const options = valueOrDefault(config.options && config.options.plugins, {}); - const plugins = allPlugins(config); - // options === false => all plugins are disabled - return options === false && !all ? [] : createDescriptors(plugins, options, all); - } + _createDescriptors(chart, all) { + const config = chart && chart.config; + const options = valueOrDefault(config.options && config.options.plugins, {}); + const plugins = allPlugins(config); + // options === false => all plugins are disabled + return options === false && !all ? [] : createDescriptors(plugins, options, all); + } - /** + /** * @param {Chart} chart * @private */ - _notifyStateChanges(chart) { - const previousDescriptors = this._oldCache || []; - const descriptors = this._cache; - const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id)); - this._notify(diff(previousDescriptors, descriptors), chart, 'stop'); - this._notify(diff(descriptors, previousDescriptors), chart, 'start'); - } + _notifyStateChanges(chart) { + const previousDescriptors = this._oldCache || []; + const descriptors = this._cache; + const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id)); + this._notify(diff(previousDescriptors, descriptors), chart, 'stop'); + this._notify(diff(descriptors, previousDescriptors), chart, 'start'); + } } /** * @param {import("./core.config").default} config */ function allPlugins(config) { - const plugins = []; - const keys = Object.keys(registry.plugins.items); - for (let i = 0; i < keys.length; i++) { - plugins.push(registry.getPlugin(keys[i])); - } - - const local = config.plugins || []; - for (let i = 0; i < local.length; i++) { - const plugin = local[i]; - - if (plugins.indexOf(plugin) === -1) { - plugins.push(plugin); - } - } - - return plugins; + const plugins = []; + const keys = Object.keys(registry.plugins.items); + for (let i = 0; i < keys.length; i++) { + plugins.push(registry.getPlugin(keys[i])); + } + + const local = config.plugins || []; + for (let i = 0; i < local.length; i++) { + const plugin = local[i]; + + if (plugins.indexOf(plugin) === -1) { + plugins.push(plugin); + } + } + + return plugins; } function getOpts(options, all) { - if (!all && options === false) { - return null; - } - if (options === true) { - return {}; - } - return options; + if (!all && options === false) { + return null; + } + if (options === true) { + return {}; + } + return options; } function createDescriptors(plugins, options, all) { - const result = []; - - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - const id = plugin.id; - const opts = getOpts(options[id], all); - if (opts === null) { - continue; - } - result.push({ - plugin, - options: mergeIf({}, [opts, defaults.plugins[id]]) - }); - } - - return result; + const result = []; + + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + const id = plugin.id; + const opts = getOpts(options[id], all); + if (opts === null) { + continue; + } + result.push({ + plugin, + options: mergeIf({}, [opts, defaults.plugins[id]]) + }); + } + + return result; } diff --git a/src/core/core.registry.js b/src/core/core.registry.js index 11f4d339f43..e46d2dc2fdd 100644 --- a/src/core/core.registry.js +++ b/src/core/core.registry.js @@ -9,177 +9,177 @@ import {each, callback as call, _capitalize} from '../helpers/helpers.core'; * Note: class is exported for typedoc */ export class Registry { - constructor() { - this.controllers = new TypedRegistry(DatasetController, 'controllers'); - this.elements = new TypedRegistry(Element, 'elements'); - this.plugins = new TypedRegistry(Object, 'plugins'); - this.scales = new TypedRegistry(Scale, 'scales'); - // Order is important, Scale has Element in prototype chain, - // so Scales must be before Elements. Plugins are a fallback, so not listed here. - this._typedRegistries = [this.controllers, this.scales, this.elements]; - } - - /** + constructor() { + this.controllers = new TypedRegistry(DatasetController, 'controllers'); + this.elements = new TypedRegistry(Element, 'elements'); + this.plugins = new TypedRegistry(Object, 'plugins'); + this.scales = new TypedRegistry(Scale, 'scales'); + // Order is important, Scale has Element in prototype chain, + // so Scales must be before Elements. Plugins are a fallback, so not listed here. + this._typedRegistries = [this.controllers, this.scales, this.elements]; + } + + /** * @param {...any} args */ - add(...args) { - this._each('register', args); - } + add(...args) { + this._each('register', args); + } - remove(...args) { - this._each('unregister', args); - } + remove(...args) { + this._each('unregister', args); + } - /** + /** * @param {...typeof DatasetController} args */ - addControllers(...args) { - this._each('register', args, this.controllers); - } + addControllers(...args) { + this._each('register', args, this.controllers); + } - /** + /** * @param {...typeof Element} args */ - addElements(...args) { - this._each('register', args, this.elements); - } + addElements(...args) { + this._each('register', args, this.elements); + } - /** + /** * @param {...any} args */ - addPlugins(...args) { - this._each('register', args, this.plugins); - } + addPlugins(...args) { + this._each('register', args, this.plugins); + } - /** + /** * @param {...typeof Scale} args */ - addScales(...args) { - this._each('register', args, this.scales); - } + addScales(...args) { + this._each('register', args, this.scales); + } - /** + /** * @param {string} id * @returns {typeof DatasetController} */ - getController(id) { - return this._get(id, this.controllers, 'controller'); - } + getController(id) { + return this._get(id, this.controllers, 'controller'); + } - /** + /** * @param {string} id * @returns {typeof Element} */ - getElement(id) { - return this._get(id, this.elements, 'element'); - } + getElement(id) { + return this._get(id, this.elements, 'element'); + } - /** + /** * @param {string} id * @returns {object} */ - getPlugin(id) { - return this._get(id, this.plugins, 'plugin'); - } + getPlugin(id) { + return this._get(id, this.plugins, 'plugin'); + } - /** + /** * @param {string} id * @returns {typeof Scale} */ - getScale(id) { - return this._get(id, this.scales, 'scale'); - } + getScale(id) { + return this._get(id, this.scales, 'scale'); + } - /** + /** * @param {...typeof DatasetController} args */ - removeControllers(...args) { - this._each('unregister', args, this.controllers); - } + removeControllers(...args) { + this._each('unregister', args, this.controllers); + } - /** + /** * @param {...typeof Element} args */ - removeElements(...args) { - this._each('unregister', args, this.elements); - } + removeElements(...args) { + this._each('unregister', args, this.elements); + } - /** + /** * @param {...any} args */ - removePlugins(...args) { - this._each('unregister', args, this.plugins); - } + removePlugins(...args) { + this._each('unregister', args, this.plugins); + } - /** + /** * @param {...typeof Scale} args */ - removeScales(...args) { - this._each('unregister', args, this.scales); - } + removeScales(...args) { + this._each('unregister', args, this.scales); + } - /** + /** * @private */ - _each(method, args, typedRegistry) { - const me = this; - [...args].forEach(arg => { - const reg = typedRegistry || me._getRegistryForType(arg); - if (typedRegistry || reg.isForType(arg) || (reg === me.plugins && arg.id)) { - me._exec(method, reg, arg); - } else { - // Handle loopable args - // Use case: - // import * as plugins from './plugins'; - // Chart.register(plugins); - each(arg, item => { - // If there are mixed types in the loopable, make sure those are - // registered in correct registry - // Use case: (treemap exporting controller, elements etc) - // import * as treemap from 'chartjs-chart-treemap'; - // Chart.register(treemap); - - const itemReg = typedRegistry || me._getRegistryForType(item); - me._exec(method, itemReg, item); - }); - } - }); - } - - /** + _each(method, args, typedRegistry) { + const me = this; + [...args].forEach(arg => { + const reg = typedRegistry || me._getRegistryForType(arg); + if (typedRegistry || reg.isForType(arg) || (reg === me.plugins && arg.id)) { + me._exec(method, reg, arg); + } else { + // Handle loopable args + // Use case: + // import * as plugins from './plugins'; + // Chart.register(plugins); + each(arg, item => { + // If there are mixed types in the loopable, make sure those are + // registered in correct registry + // Use case: (treemap exporting controller, elements etc) + // import * as treemap from 'chartjs-chart-treemap'; + // Chart.register(treemap); + + const itemReg = typedRegistry || me._getRegistryForType(item); + me._exec(method, itemReg, item); + }); + } + }); + } + + /** * @private */ - _exec(method, registry, component) { - const camelMethod = _capitalize(method); - call(component['before' + camelMethod], [], component); - registry[method](component); - call(component['after' + camelMethod], [], component); - } + _exec(method, registry, component) { + const camelMethod = _capitalize(method); + call(component['before' + camelMethod], [], component); + registry[method](component); + call(component['after' + camelMethod], [], component); + } - /** + /** * @private */ - _getRegistryForType(type) { - for (let i = 0; i < this._typedRegistries.length; i++) { - const reg = this._typedRegistries[i]; - if (reg.isForType(type)) { - return reg; - } - } - // plugins is the fallback registry - return this.plugins; - } - - /** + _getRegistryForType(type) { + for (let i = 0; i < this._typedRegistries.length; i++) { + const reg = this._typedRegistries[i]; + if (reg.isForType(type)) { + return reg; + } + } + // plugins is the fallback registry + return this.plugins; + } + + /** * @private */ - _get(id, typedRegistry, type) { - const item = typedRegistry.get(id); - if (item === undefined) { - throw new Error('"' + id + '" is not a registered ' + type + '.'); - } - return item; - } + _get(id, typedRegistry, type) { + const item = typedRegistry.get(id); + if (item === undefined) { + throw new Error('"' + id + '" is not a registered ' + type + '.'); + } + return item; + } } diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 94b15468b97..70fdfaca695 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -12,67 +12,67 @@ import Ticks from './core.ticks'; */ defaults.set('scale', { - display: true, - offset: false, - reverse: false, - beginAtZero: false, + display: true, + offset: false, + reverse: false, + beginAtZero: false, - /** + /** * Scale boundary strategy (bypassed by min/max time options) * - `data`: make sure data are fully visible, ticks outside are removed * - `ticks`: make sure ticks are fully visible, data outside are truncated * @see https://github.com/chartjs/Chart.js/pull/4556 * @since 3.0.0 */ - bounds: 'ticks', - - // grid line settings - gridLines: { - display: true, - lineWidth: 1, - drawBorder: true, - drawOnChartArea: true, - drawTicks: true, - tickLength: 10, - offsetGridLines: false, - borderDash: [], - borderDashOffset: 0.0 - }, - - // scale label - scaleLabel: { - // display property - display: false, - - // actual label - labelString: '', - - // top/bottom padding - padding: { - top: 4, - bottom: 4 - } - }, - - // label settings - ticks: { - minRotation: 0, - maxRotation: 50, - mirror: false, - textStrokeWidth: 0, - textStrokeColor: '', - padding: 0, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: Ticks.formatters.values, - minor: {}, - major: {}, - align: 'center', - crossAlign: 'near', - } + bounds: 'ticks', + + // grid line settings + gridLines: { + display: true, + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickLength: 10, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + minRotation: 0, + maxRotation: 50, + mirror: false, + textStrokeWidth: 0, + textStrokeColor: '', + padding: 0, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Ticks.formatters.values, + minor: {}, + major: {}, + align: 'center', + crossAlign: 'near', + } }); defaults.route('scale.ticks', 'color', '', 'color'); @@ -85,15 +85,15 @@ defaults.route('scale.scaleLabel', 'color', '', 'color'); * @param {number} numItems */ function sample(arr, numItems) { - const result = []; - const increment = arr.length / numItems; - const len = arr.length; - let i = 0; - - for (; i < len; i += increment) { - result.push(arr[Math.floor(i)]); - } - return result; + const result = []; + const increment = arr.length / numItems; + const len = arr.length; + let i = 0; + + for (; i < len; i += increment) { + result.push(arr[Math.floor(i)]); + } + return result; } /** @@ -102,30 +102,30 @@ function sample(arr, numItems) { * @param {boolean} offsetGridLines */ function getPixelForGridLine(scale, index, offsetGridLines) { - const length = scale.ticks.length; - const validIndex = Math.min(index, length - 1); - const start = scale._startPixel; - const end = scale._endPixel; - const epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - let lineValue = scale.getPixelForTick(validIndex); - let offset; - - if (offsetGridLines) { - if (length === 1) { - offset = Math.max(lineValue - start, end - lineValue); - } else if (index === 0) { - offset = (scale.getPixelForTick(1) - lineValue) / 2; - } else { - offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; - } - lineValue += validIndex < index ? offset : -offset; - - // Return undefined if the pixel is out of the range - if (lineValue < start - epsilon || lineValue > end + epsilon) { - return; - } - } - return lineValue; + const length = scale.ticks.length; + const validIndex = Math.min(index, length - 1); + const start = scale._startPixel; + const end = scale._endPixel; + const epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + let lineValue = scale.getPixelForTick(validIndex); + let offset; + + if (offsetGridLines) { + if (length === 1) { + offset = Math.max(lineValue - start, end - lineValue); + } else if (index === 0) { + offset = (scale.getPixelForTick(1) - lineValue) / 2; + } else { + offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; + } + lineValue += validIndex < index ? offset : -offset; + + // Return undefined if the pixel is out of the range + if (lineValue < start - epsilon || lineValue > end + epsilon) { + return; + } + } + return lineValue; } /** @@ -133,57 +133,57 @@ function getPixelForGridLine(scale, index, offsetGridLines) { * @param {number} length */ function garbageCollect(caches, length) { - each(caches, (cache) => { - const gc = cache.gc; - const gcLen = gc.length / 2; - let i; - if (gcLen > length) { - for (i = 0; i < gcLen; ++i) { - delete cache.data[gc[i]]; - } - gc.splice(0, gcLen); - } - }); + each(caches, (cache) => { + const gc = cache.gc; + const gcLen = gc.length / 2; + let i; + if (gcLen > length) { + for (i = 0; i < gcLen; ++i) { + delete cache.data[gc[i]]; + } + gc.splice(0, gcLen); + } + }); } /** * @param {object} options */ function getTickMarkLength(options) { - return options.drawTicks ? options.tickLength : 0; + return options.drawTicks ? options.tickLength : 0; } /** * @param {object} options */ function getScaleLabelHeight(options, fallback) { - if (!options.display) { - return 0; - } + if (!options.display) { + return 0; + } - const font = toFont(options.font, fallback); - const padding = toPadding(options.padding); + const font = toFont(options.font, fallback); + const padding = toPadding(options.padding); - return font.lineHeight + padding.height; + return font.lineHeight + padding.height; } /** * @param {number[]} arr */ function getEvenSpacing(arr) { - const len = arr.length; - let i, diff; - - if (len < 2) { - return false; - } - - for (diff = arr[0], i = 1; i < len; ++i) { - if (arr[i] - arr[i - 1] !== diff) { - return false; - } - } - return diff; + const len = arr.length; + let i, diff; + + if (len < 2) { + return false; + } + + for (diff = arr[0], i = 1; i < len; ++i) { + if (arr[i] - arr[i - 1] !== diff) { + return false; + } + } + return diff; } /** @@ -192,37 +192,37 @@ function getEvenSpacing(arr) { * @param {number} ticksLimit */ function calculateSpacing(majorIndices, ticks, ticksLimit) { - const evenMajorSpacing = getEvenSpacing(majorIndices); - const spacing = ticks.length / ticksLimit; - - // If the major ticks are evenly spaced apart, place the minor ticks - // so that they divide the major ticks into even chunks - if (!evenMajorSpacing) { - return Math.max(spacing, 1); - } - - const factors = _factorize(evenMajorSpacing); - for (let i = 0, ilen = factors.length - 1; i < ilen; i++) { - const factor = factors[i]; - if (factor > spacing) { - return factor; - } - } - return Math.max(spacing, 1); + const evenMajorSpacing = getEvenSpacing(majorIndices); + const spacing = ticks.length / ticksLimit; + + // If the major ticks are evenly spaced apart, place the minor ticks + // so that they divide the major ticks into even chunks + if (!evenMajorSpacing) { + return Math.max(spacing, 1); + } + + const factors = _factorize(evenMajorSpacing); + for (let i = 0, ilen = factors.length - 1; i < ilen; i++) { + const factor = factors[i]; + if (factor > spacing) { + return factor; + } + } + return Math.max(spacing, 1); } /** * @param {Tick[]} ticks */ function getMajorIndices(ticks) { - const result = []; - let i, ilen; - for (i = 0, ilen = ticks.length; i < ilen; i++) { - if (ticks[i].major) { - result.push(i); - } - } - return result; + const result = []; + let i, ilen; + for (i = 0, ilen = ticks.length; i < ilen; i++) { + if (ticks[i].major) { + result.push(i); + } + } + return result; } /** @@ -232,18 +232,18 @@ function getMajorIndices(ticks) { * @param {number} spacing */ function skipMajors(ticks, newTicks, majorIndices, spacing) { - let count = 0; - let next = majorIndices[0]; - let i; - - spacing = Math.ceil(spacing); - for (i = 0; i < ticks.length; i++) { - if (i === next) { - newTicks.push(ticks[i]); - count++; - next = majorIndices[count * spacing]; - } - } + let count = 0; + let next = majorIndices[0]; + let i; + + spacing = Math.ceil(spacing); + for (i = 0; i < ticks.length; i++) { + if (i === next) { + newTicks.push(ticks[i]); + count++; + next = majorIndices[count * spacing]; + } + } } /** @@ -254,266 +254,266 @@ function skipMajors(ticks, newTicks, majorIndices, spacing) { * @param {number} [majorEnd] */ function skip(ticks, newTicks, spacing, majorStart, majorEnd) { - const start = valueOrDefault(majorStart, 0); - const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length); - let count = 0; - let length, i, next; - - spacing = Math.ceil(spacing); - if (majorEnd) { - length = majorEnd - majorStart; - spacing = length / Math.floor(length / spacing); - } - - next = start; - - while (next < 0) { - count++; - next = Math.round(start + count * spacing); - } - - for (i = Math.max(start, 0); i < end; i++) { - if (i === next) { - newTicks.push(ticks[i]); - count++; - next = Math.round(start + count * spacing); - } - } + const start = valueOrDefault(majorStart, 0); + const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length); + let count = 0; + let length, i, next; + + spacing = Math.ceil(spacing); + if (majorEnd) { + length = majorEnd - majorStart; + spacing = length / Math.floor(length / spacing); + } + + next = start; + + while (next < 0) { + count++; + next = Math.round(start + count * spacing); + } + + for (i = Math.max(start, 0); i < end; i++) { + if (i === next) { + newTicks.push(ticks[i]); + count++; + next = Math.round(start + count * spacing); + } + } } function createScaleContext(parent, scale) { - return Object.create(parent, { - scale: { - value: scale - }, - type: { - value: 'scale' - } - }); + return Object.create(parent, { + scale: { + value: scale + }, + type: { + value: 'scale' + } + }); } function createTickContext(parent, index, tick) { - return Object.create(parent, { - tick: { - value: tick - }, - index: { - value: index - }, - type: { - value: 'tick' - } - }); + return Object.create(parent, { + tick: { + value: tick + }, + index: { + value: index + }, + type: { + value: 'tick' + } + }); } export default class Scale extends Element { - // eslint-disable-next-line max-statements - constructor(cfg) { - super(); - - /** @type {string} */ - this.id = cfg.id; - /** @type {string} */ - this.type = cfg.type; - /** @type {object} */ - this.options = undefined; - /** @type {CanvasRenderingContext2D} */ - this.ctx = cfg.ctx; - /** @type {Chart} */ - this.chart = cfg.chart; - - // implements box - /** @type {number} */ - this.top = undefined; - /** @type {number} */ - this.bottom = undefined; - /** @type {number} */ - this.left = undefined; - /** @type {number} */ - this.right = undefined; - /** @type {number} */ - this.width = undefined; - /** @type {number} */ - this.height = undefined; - this._margins = { - left: 0, - right: 0, - top: 0, - bottom: 0 - }; - /** @type {number} */ - this.maxWidth = undefined; - /** @type {number} */ - this.maxHeight = undefined; - /** @type {number} */ - this.paddingTop = undefined; - /** @type {number} */ - this.paddingBottom = undefined; - /** @type {number} */ - this.paddingLeft = undefined; - /** @type {number} */ - this.paddingRight = undefined; - - // scale-specific properties - /** @type {string=} */ - this.axis = undefined; - /** @type {number=} */ - this.labelRotation = undefined; - this.min = undefined; - this.max = undefined; - /** @type {Tick[]} */ - this.ticks = []; - /** @type {object[]|null} */ - this._gridLineItems = null; - /** @type {object[]|null} */ - this._labelItems = null; - /** @type {object|null} */ - this._labelSizes = null; - this._length = 0; - this._longestTextCache = {}; - /** @type {number} */ - this._startPixel = undefined; - /** @type {number} */ - this._endPixel = undefined; - this._reversePixels = false; - this._userMax = undefined; - this._userMin = undefined; - this._suggestedMax = undefined; - this._suggestedMin = undefined; - this._ticksLength = 0; - this._borderValue = 0; - this._cache = {}; - this._dataLimitsCached = false; - this.$context = undefined; - } - - /** + // eslint-disable-next-line max-statements + constructor(cfg) { + super(); + + /** @type {string} */ + this.id = cfg.id; + /** @type {string} */ + this.type = cfg.type; + /** @type {object} */ + this.options = undefined; + /** @type {CanvasRenderingContext2D} */ + this.ctx = cfg.ctx; + /** @type {Chart} */ + this.chart = cfg.chart; + + // implements box + /** @type {number} */ + this.top = undefined; + /** @type {number} */ + this.bottom = undefined; + /** @type {number} */ + this.left = undefined; + /** @type {number} */ + this.right = undefined; + /** @type {number} */ + this.width = undefined; + /** @type {number} */ + this.height = undefined; + this._margins = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }; + /** @type {number} */ + this.maxWidth = undefined; + /** @type {number} */ + this.maxHeight = undefined; + /** @type {number} */ + this.paddingTop = undefined; + /** @type {number} */ + this.paddingBottom = undefined; + /** @type {number} */ + this.paddingLeft = undefined; + /** @type {number} */ + this.paddingRight = undefined; + + // scale-specific properties + /** @type {string=} */ + this.axis = undefined; + /** @type {number=} */ + this.labelRotation = undefined; + this.min = undefined; + this.max = undefined; + /** @type {Tick[]} */ + this.ticks = []; + /** @type {object[]|null} */ + this._gridLineItems = null; + /** @type {object[]|null} */ + this._labelItems = null; + /** @type {object|null} */ + this._labelSizes = null; + this._length = 0; + this._longestTextCache = {}; + /** @type {number} */ + this._startPixel = undefined; + /** @type {number} */ + this._endPixel = undefined; + this._reversePixels = false; + this._userMax = undefined; + this._userMin = undefined; + this._suggestedMax = undefined; + this._suggestedMin = undefined; + this._ticksLength = 0; + this._borderValue = 0; + this._cache = {}; + this._dataLimitsCached = false; + this.$context = undefined; + } + + /** * @param {object} options * @since 3.0 */ - init(options) { - const me = this; - me.options = options; + init(options) { + const me = this; + me.options = options; - me.axis = me.isHorizontal() ? 'x' : 'y'; + me.axis = me.isHorizontal() ? 'x' : 'y'; - // parse min/max value, so we can properly determine min/max for other scales - me._userMin = me.parse(options.min); - me._userMax = me.parse(options.max); - me._suggestedMin = me.parse(options.suggestedMin); - me._suggestedMax = me.parse(options.suggestedMax); - } + // parse min/max value, so we can properly determine min/max for other scales + me._userMin = me.parse(options.min); + me._userMax = me.parse(options.max); + me._suggestedMin = me.parse(options.suggestedMin); + me._suggestedMax = me.parse(options.suggestedMax); + } - /** + /** * Parse a supported input value to internal representation. * @param {*} raw * @param {number} [index] * @since 3.0 */ - parse(raw, index) { // eslint-disable-line no-unused-vars - return raw; - } + parse(raw, index) { // eslint-disable-line no-unused-vars + return raw; + } - /** + /** * @return {{min: number, max: number, minDefined: boolean, maxDefined: boolean}} * @protected * @since 3.0 */ - getUserBounds() { - let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this; - _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY); - _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY); - _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY); - _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY); - return { - min: finiteOrDefault(_userMin, _suggestedMin), - max: finiteOrDefault(_userMax, _suggestedMax), - minDefined: isFinite(_userMin), - maxDefined: isFinite(_userMax) - }; - } - - /** + getUserBounds() { + let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this; + _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY); + _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY); + _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY); + _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY); + return { + min: finiteOrDefault(_userMin, _suggestedMin), + max: finiteOrDefault(_userMax, _suggestedMax), + minDefined: isFinite(_userMin), + maxDefined: isFinite(_userMax) + }; + } + + /** * @param {boolean} canStack * @return {{min: number, max: number}} * @protected * @since 3.0 */ - getMinMax(canStack) { - const me = this; - // eslint-disable-next-line prefer-const - let {min, max, minDefined, maxDefined} = me.getUserBounds(); - let range; - - if (minDefined && maxDefined) { - return {min, max}; - } - - const metas = me.getMatchingVisibleMetas(); - for (let i = 0, ilen = metas.length; i < ilen; ++i) { - range = metas[i].controller.getMinMax(me, canStack); - if (!minDefined) { - min = Math.min(min, range.min); - } - if (!maxDefined) { - max = Math.max(max, range.max); - } - } - - return { - min: finiteOrDefault(min, finiteOrDefault(max, min)), - max: finiteOrDefault(max, finiteOrDefault(min, max)) - }; - } - - /** + getMinMax(canStack) { + const me = this; + // eslint-disable-next-line prefer-const + let {min, max, minDefined, maxDefined} = me.getUserBounds(); + let range; + + if (minDefined && maxDefined) { + return {min, max}; + } + + const metas = me.getMatchingVisibleMetas(); + for (let i = 0, ilen = metas.length; i < ilen; ++i) { + range = metas[i].controller.getMinMax(me, canStack); + if (!minDefined) { + min = Math.min(min, range.min); + } + if (!maxDefined) { + max = Math.max(max, range.max); + } + } + + return { + min: finiteOrDefault(min, finiteOrDefault(max, min)), + max: finiteOrDefault(max, finiteOrDefault(min, max)) + }; + } + + /** * Get the padding needed for the scale * @return {{top: number, left: number, bottom: number, right: number}} the necessary padding * @private */ - getPadding() { - const me = this; - return { - left: me.paddingLeft || 0, - top: me.paddingTop || 0, - right: me.paddingRight || 0, - bottom: me.paddingBottom || 0 - }; - } - - /** + getPadding() { + const me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + } + + /** * Returns the scale tick objects * @return {Tick[]} * @since 2.7 */ - getTicks() { - return this.ticks; - } + getTicks() { + return this.ticks; + } - /** + /** * @return {string[]} */ - getLabels() { - const data = this.chart.data; - return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; - } - - // When a new layout is created, reset the data limits cache - beforeLayout() { - this._cache = {}; - this._dataLimitsCached = false; - } - - // These methods are ordered by lifecycle. Utilities then follow. - // Any function defined here is inherited by all scale types. - // Any function can be extended by the scale type - - beforeUpdate() { - call(this.options.beforeUpdate, [this]); - } - - /** + getLabels() { + const data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; + } + + // When a new layout is created, reset the data limits cache + beforeLayout() { + this._cache = {}; + this._dataLimitsCached = false; + } + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + beforeUpdate() { + call(this.options.beforeUpdate, [this]); + } + + /** * @param {number} maxWidth - the max width in pixels * @param {number} maxHeight - the max height in pixels * @param {{top: number, left: number, bottom: number, right: number}} margins - the space between the edge of the other scales and edge of the chart @@ -521,1279 +521,1279 @@ export default class Scale extends Element { * - padding - space that's required to show the labels at the edges of the scale * - thickness of scales or legends in another orientation */ - update(maxWidth, maxHeight, margins) { - const me = this; - const tickOpts = me.options.ticks; - const sampleSize = tickOpts.sampleSize; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me._margins = Object.assign({ - left: 0, - right: 0, - top: 0, - bottom: 0 - }, margins); - - me.ticks = null; - me._labelSizes = null; - me._gridLineItems = null; - me._labelItems = null; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - - // Data min/max - if (!me._dataLimitsCached) { - me.beforeDataLimits(); - me.determineDataLimits(); - me.afterDataLimits(); - me._dataLimitsCached = true; - } - - me.beforeBuildTicks(); - - me.ticks = me.buildTicks() || []; - - // Allow modification of ticks in callback. - me.afterBuildTicks(); - - // Compute tick rotation and fit using a sampled subset of labels - // We generally don't need to compute the size of every single label for determining scale size - const samplingEnabled = sampleSize < me.ticks.length; - me._convertTicksToLabels(samplingEnabled ? sample(me.ticks, sampleSize) : me.ticks); - - // configure is called twice, once here, once from core.controller.updateLayout. - // Here we haven't been positioned yet, but dimensions are correct. - // Variables set in configure are needed for calculateLabelRotation, and - // it's ok that coordinates are not correct there, only dimensions matter. - me.configure(); - - // Tick Rotation - me.beforeCalculateLabelRotation(); - me.calculateLabelRotation(); // Preconditions: number of ticks and sizes of largest labels must be calculated beforehand - me.afterCalculateLabelRotation(); - - me.beforeFit(); - me.fit(); // Preconditions: label rotation and label sizes must be calculated beforehand - me.afterFit(); - - // Auto-skip - me.ticks = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(me.ticks) : me.ticks; - - if (samplingEnabled) { - // Generate labels using all non-skipped ticks - me._convertTicksToLabels(me.ticks); - } - - // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! - - me.afterUpdate(); - } - - /** + update(maxWidth, maxHeight, margins) { + const me = this; + const tickOpts = me.options.ticks; + const sampleSize = tickOpts.sampleSize; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me._margins = Object.assign({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + + me.ticks = null; + me._labelSizes = null; + me._gridLineItems = null; + me._labelItems = null; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + if (!me._dataLimitsCached) { + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + me._dataLimitsCached = true; + } + + me.beforeBuildTicks(); + + me.ticks = me.buildTicks() || []; + + // Allow modification of ticks in callback. + me.afterBuildTicks(); + + // Compute tick rotation and fit using a sampled subset of labels + // We generally don't need to compute the size of every single label for determining scale size + const samplingEnabled = sampleSize < me.ticks.length; + me._convertTicksToLabels(samplingEnabled ? sample(me.ticks, sampleSize) : me.ticks); + + // configure is called twice, once here, once from core.controller.updateLayout. + // Here we haven't been positioned yet, but dimensions are correct. + // Variables set in configure are needed for calculateLabelRotation, and + // it's ok that coordinates are not correct there, only dimensions matter. + me.configure(); + + // Tick Rotation + me.beforeCalculateLabelRotation(); + me.calculateLabelRotation(); // Preconditions: number of ticks and sizes of largest labels must be calculated beforehand + me.afterCalculateLabelRotation(); + + me.beforeFit(); + me.fit(); // Preconditions: label rotation and label sizes must be calculated beforehand + me.afterFit(); + + // Auto-skip + me.ticks = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(me.ticks) : me.ticks; + + if (samplingEnabled) { + // Generate labels using all non-skipped ticks + me._convertTicksToLabels(me.ticks); + } + + // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! + + me.afterUpdate(); + } + + /** * @protected */ - configure() { - const me = this; - let reversePixels = me.options.reverse; - let startPixel, endPixel; - - if (me.isHorizontal()) { - startPixel = me.left; - endPixel = me.right; - } else { - startPixel = me.top; - endPixel = me.bottom; - // by default vertical scales are from bottom to top, so pixels are reversed - reversePixels = !reversePixels; - } - me._startPixel = startPixel; - me._endPixel = endPixel; - me._reversePixels = reversePixels; - me._length = endPixel - startPixel; - } - - afterUpdate() { - call(this.options.afterUpdate, [this]); - } - - // - - beforeSetDimensions() { - call(this.options.beforeSetDimensions, [this]); - } - setDimensions() { - const me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - } - afterSetDimensions() { - call(this.options.afterSetDimensions, [this]); - } - - _callHooks(name) { - const me = this; - me.chart.notifyPlugins(name, me.getContext()); - call(me.options[name], [me]); - } - - // Data limits - beforeDataLimits() { - this._callHooks('beforeDataLimits'); - } - determineDataLimits() {} - afterDataLimits() { - this._callHooks('afterDataLimits'); - } - - // - beforeBuildTicks() { - this._callHooks('beforeBuildTicks'); - } - /** + configure() { + const me = this; + let reversePixels = me.options.reverse; + let startPixel, endPixel; + + if (me.isHorizontal()) { + startPixel = me.left; + endPixel = me.right; + } else { + startPixel = me.top; + endPixel = me.bottom; + // by default vertical scales are from bottom to top, so pixels are reversed + reversePixels = !reversePixels; + } + me._startPixel = startPixel; + me._endPixel = endPixel; + me._reversePixels = reversePixels; + me._length = endPixel - startPixel; + } + + afterUpdate() { + call(this.options.afterUpdate, [this]); + } + + // + + beforeSetDimensions() { + call(this.options.beforeSetDimensions, [this]); + } + setDimensions() { + const me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + } + afterSetDimensions() { + call(this.options.afterSetDimensions, [this]); + } + + _callHooks(name) { + const me = this; + me.chart.notifyPlugins(name, me.getContext()); + call(me.options[name], [me]); + } + + // Data limits + beforeDataLimits() { + this._callHooks('beforeDataLimits'); + } + determineDataLimits() {} + afterDataLimits() { + this._callHooks('afterDataLimits'); + } + + // + beforeBuildTicks() { + this._callHooks('beforeBuildTicks'); + } + /** * @return {object[]} the ticks */ - buildTicks() { - return []; - } - afterBuildTicks() { - this._callHooks('afterBuildTicks'); - } - - beforeTickToLabelConversion() { - call(this.options.beforeTickToLabelConversion, [this]); - } - /** + buildTicks() { + return []; + } + afterBuildTicks() { + this._callHooks('afterBuildTicks'); + } + + beforeTickToLabelConversion() { + call(this.options.beforeTickToLabelConversion, [this]); + } + /** * Convert ticks to label strings * @param {Tick[]} ticks */ - generateTickLabels(ticks) { - const me = this; - const tickOpts = me.options.ticks; - let i, ilen, tick; - for (i = 0, ilen = ticks.length; i < ilen; i++) { - tick = ticks[i]; - tick.label = call(tickOpts.callback, [tick.value, i, ticks], me); - } - } - afterTickToLabelConversion() { - call(this.options.afterTickToLabelConversion, [this]); - } - - // - - beforeCalculateLabelRotation() { - call(this.options.beforeCalculateLabelRotation, [this]); - } - calculateLabelRotation() { - const me = this; - const options = me.options; - const tickOpts = options.ticks; - const numTicks = me.ticks.length; - const minRotation = tickOpts.minRotation || 0; - const maxRotation = tickOpts.maxRotation; - let labelRotation = minRotation; - let tickWidth, maxHeight, maxLabelDiagonal; - - if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { - me.labelRotation = minRotation; - return; - } - - const labelSizes = me._getLabelSizes(); - const maxLabelWidth = labelSizes.widest.width; - const maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; - - // Estimate the width of each grid based on the canvas width, the maximum - // label width and the number of tick intervals - const maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); - tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); - - // Allow 3 pixels x2 padding either side for label readability - if (maxLabelWidth + 6 > tickWidth) { - tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); - maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) + generateTickLabels(ticks) { + const me = this; + const tickOpts = me.options.ticks; + let i, ilen, tick; + for (i = 0, ilen = ticks.length; i < ilen; i++) { + tick = ticks[i]; + tick.label = call(tickOpts.callback, [tick.value, i, ticks], me); + } + } + afterTickToLabelConversion() { + call(this.options.afterTickToLabelConversion, [this]); + } + + // + + beforeCalculateLabelRotation() { + call(this.options.beforeCalculateLabelRotation, [this]); + } + calculateLabelRotation() { + const me = this; + const options = me.options; + const tickOpts = options.ticks; + const numTicks = me.ticks.length; + const minRotation = tickOpts.minRotation || 0; + const maxRotation = tickOpts.maxRotation; + let labelRotation = minRotation; + let tickWidth, maxHeight, maxLabelDiagonal; + + if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { + me.labelRotation = minRotation; + return; + } + + const labelSizes = me._getLabelSizes(); + const maxLabelWidth = labelSizes.widest.width; + const maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; + + // Estimate the width of each grid based on the canvas width, the maximum + // label width and the number of tick intervals + const maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); + tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); + + // Allow 3 pixels x2 padding either side for label readability + if (maxLabelWidth + 6 > tickWidth) { + tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); + maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) - tickOpts.padding - getScaleLabelHeight(options.scaleLabel, me.chart.options.font); - maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); - labelRotation = toDegrees(Math.min( - Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), - Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) - )); - labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); - } - - me.labelRotation = labelRotation; - } - afterCalculateLabelRotation() { - call(this.options.afterCalculateLabelRotation, [this]); - } - - // - - beforeFit() { - call(this.options.beforeFit, [this]); - } - fit() { - const me = this; - // Reset - const minSize = { - width: 0, - height: 0 - }; - - const chart = me.chart; - const opts = me.options; - const tickOpts = opts.ticks; - const scaleLabelOpts = opts.scaleLabel; - const gridLineOpts = opts.gridLines; - const display = me._isVisible(); - const labelsBelowTicks = opts.position !== 'top' && me.axis === 'x'; - const isHorizontal = me.isHorizontal(); - const scaleLabelHeight = display && getScaleLabelHeight(scaleLabelOpts, chart.options.font); - - // Width - if (isHorizontal) { - minSize.width = me.maxWidth; - } else if (display) { - minSize.width = getTickMarkLength(gridLineOpts) + scaleLabelHeight; - } - - // height - if (!isHorizontal) { - minSize.height = me.maxHeight; // fill all the height - } else if (display) { - minSize.height = getTickMarkLength(gridLineOpts) + scaleLabelHeight; - } - - // Don't bother fitting the ticks if we are not showing the labels - if (tickOpts.display && display && me.ticks.length) { - const labelSizes = me._getLabelSizes(); - const firstLabelSize = labelSizes.first; - const lastLabelSize = labelSizes.last; - const widestLabelSize = labelSizes.widest; - const highestLabelSize = labelSizes.highest; - const lineSpace = highestLabelSize.offset * 0.8; - const tickPadding = tickOpts.padding; - - if (isHorizontal) { - // A horizontal axis is more constrained by the height. - const isRotated = me.labelRotation !== 0; - const angleRadians = toRadians(me.labelRotation); - const cosRotation = Math.cos(angleRadians); - const sinRotation = Math.sin(angleRadians); - - const labelHeight = sinRotation * widestLabelSize.width + maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); + labelRotation = toDegrees(Math.min( + Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), + Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) + )); + labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); + } + + me.labelRotation = labelRotation; + } + afterCalculateLabelRotation() { + call(this.options.afterCalculateLabelRotation, [this]); + } + + // + + beforeFit() { + call(this.options.beforeFit, [this]); + } + fit() { + const me = this; + // Reset + const minSize = { + width: 0, + height: 0 + }; + + const chart = me.chart; + const opts = me.options; + const tickOpts = opts.ticks; + const scaleLabelOpts = opts.scaleLabel; + const gridLineOpts = opts.gridLines; + const display = me._isVisible(); + const labelsBelowTicks = opts.position !== 'top' && me.axis === 'x'; + const isHorizontal = me.isHorizontal(); + const scaleLabelHeight = display && getScaleLabelHeight(scaleLabelOpts, chart.options.font); + + // Width + if (isHorizontal) { + minSize.width = me.maxWidth; + } else if (display) { + minSize.width = getTickMarkLength(gridLineOpts) + scaleLabelHeight; + } + + // height + if (!isHorizontal) { + minSize.height = me.maxHeight; // fill all the height + } else if (display) { + minSize.height = getTickMarkLength(gridLineOpts) + scaleLabelHeight; + } + + // Don't bother fitting the ticks if we are not showing the labels + if (tickOpts.display && display && me.ticks.length) { + const labelSizes = me._getLabelSizes(); + const firstLabelSize = labelSizes.first; + const lastLabelSize = labelSizes.last; + const widestLabelSize = labelSizes.widest; + const highestLabelSize = labelSizes.highest; + const lineSpace = highestLabelSize.offset * 0.8; + const tickPadding = tickOpts.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + const isRotated = me.labelRotation !== 0; + const angleRadians = toRadians(me.labelRotation); + const cosRotation = Math.cos(angleRadians); + const sinRotation = Math.sin(angleRadians); + + const labelHeight = sinRotation * widestLabelSize.width + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) + (isRotated ? 0 : lineSpace); // padding - minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - - const offsetLeft = me.getPixelForTick(0) - me.left; - const offsetRight = me.right - me.getPixelForTick(me.ticks.length - 1); - let paddingLeft, paddingRight; - - // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned - // which means that the right padding is dominated by the font height - if (isRotated) { - paddingLeft = labelsBelowTicks ? - cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : - sinRotation * (firstLabelSize.height - firstLabelSize.offset); - paddingRight = labelsBelowTicks ? - sinRotation * (lastLabelSize.height - lastLabelSize.offset) : - cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; - } else if (tickOpts.align === 'start') { - paddingLeft = 0; - paddingRight = lastLabelSize.width; - } else if (tickOpts.align === 'end') { - paddingLeft = firstLabelSize.width; - paddingRight = 0; - } else { - paddingLeft = firstLabelSize.width / 2; - paddingRight = lastLabelSize.width / 2; - } - - // Adjust padding taking into account changes in offsets - // and add 3 px to move away from canvas edges - me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; - me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; - } else { - // A vertical axis is more constrained by the width. Labels are the - // dominant factor here, so get that length first and account for padding - const labelWidth = tickOpts.mirror ? 0 : - // use lineSpace for consistency with horizontal axis - // tickPadding is not implemented for horizontal - widestLabelSize.width + tickPadding + lineSpace; - - minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - - let paddingTop = lastLabelSize.height / 2; - let paddingBottom = firstLabelSize.height / 2; - - if (tickOpts.align === 'start') { - paddingTop = 0; - paddingBottom = firstLabelSize.height; - } else if (tickOpts.align === 'end') { - paddingTop = lastLabelSize.height; - paddingBottom = 0; - } - - me.paddingTop = paddingTop; - me.paddingBottom = paddingBottom; - } - } - - me._handleMargins(); - - if (isHorizontal) { - me.width = me._length = chart.width - me._margins.left - me._margins.right; - me.height = minSize.height; - } else { - me.width = minSize.width; - me.height = me._length = chart.height - me._margins.top - me._margins.bottom; - } - } - - /** + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + const offsetLeft = me.getPixelForTick(0) - me.left; + const offsetRight = me.right - me.getPixelForTick(me.ticks.length - 1); + let paddingLeft, paddingRight; + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (isRotated) { + paddingLeft = labelsBelowTicks ? + cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : + sinRotation * (firstLabelSize.height - firstLabelSize.offset); + paddingRight = labelsBelowTicks ? + sinRotation * (lastLabelSize.height - lastLabelSize.offset) : + cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; + } else if (tickOpts.align === 'start') { + paddingLeft = 0; + paddingRight = lastLabelSize.width; + } else if (tickOpts.align === 'end') { + paddingLeft = firstLabelSize.width; + paddingRight = 0; + } else { + paddingLeft = firstLabelSize.width / 2; + paddingRight = lastLabelSize.width / 2; + } + + // Adjust padding taking into account changes in offsets + // and add 3 px to move away from canvas edges + me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; + me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + const labelWidth = tickOpts.mirror ? 0 : + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + widestLabelSize.width + tickPadding + lineSpace; + + minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); + + let paddingTop = lastLabelSize.height / 2; + let paddingBottom = firstLabelSize.height / 2; + + if (tickOpts.align === 'start') { + paddingTop = 0; + paddingBottom = firstLabelSize.height; + } else if (tickOpts.align === 'end') { + paddingTop = lastLabelSize.height; + paddingBottom = 0; + } + + me.paddingTop = paddingTop; + me.paddingBottom = paddingBottom; + } + } + + me._handleMargins(); + + if (isHorizontal) { + me.width = me._length = chart.width - me._margins.left - me._margins.right; + me.height = minSize.height; + } else { + me.width = minSize.width; + me.height = me._length = chart.height - me._margins.top - me._margins.bottom; + } + } + + /** * Handle margins and padding interactions * @private */ - _handleMargins() { - const me = this; - if (me._margins) { - me._margins.left = Math.max(me.paddingLeft, me._margins.left); - me._margins.top = Math.max(me.paddingTop, me._margins.top); - me._margins.right = Math.max(me.paddingRight, me._margins.right); - me._margins.bottom = Math.max(me.paddingBottom, me._margins.bottom); - } - } - - afterFit() { - call(this.options.afterFit, [this]); - } - - // Shared Methods - /** + _handleMargins() { + const me = this; + if (me._margins) { + me._margins.left = Math.max(me.paddingLeft, me._margins.left); + me._margins.top = Math.max(me.paddingTop, me._margins.top); + me._margins.right = Math.max(me.paddingRight, me._margins.right); + me._margins.bottom = Math.max(me.paddingBottom, me._margins.bottom); + } + } + + afterFit() { + call(this.options.afterFit, [this]); + } + + // Shared Methods + /** * @return {boolean} */ - isHorizontal() { - const {axis, position} = this.options; - return position === 'top' || position === 'bottom' || axis === 'x'; - } - /** + isHorizontal() { + const {axis, position} = this.options; + return position === 'top' || position === 'bottom' || axis === 'x'; + } + /** * @return {boolean} */ - isFullSize() { - return this.options.fullSize; - } + isFullSize() { + return this.options.fullSize; + } - /** + /** * @param {Tick[]} ticks * @private */ - _convertTicksToLabels(ticks) { - const me = this; + _convertTicksToLabels(ticks) { + const me = this; - me.beforeTickToLabelConversion(); + me.beforeTickToLabelConversion(); - me.generateTickLabels(ticks); + me.generateTickLabels(ticks); - me.afterTickToLabelConversion(); - } + me.afterTickToLabelConversion(); + } - /** + /** * @return {{ first: object, last: object, widest: object, highest: object }} * @private */ - _getLabelSizes() { - const me = this; - let labelSizes = me._labelSizes; + _getLabelSizes() { + const me = this; + let labelSizes = me._labelSizes; - if (!labelSizes) { - me._labelSizes = labelSizes = me._computeLabelSizes(); - } + if (!labelSizes) { + me._labelSizes = labelSizes = me._computeLabelSizes(); + } - return labelSizes; - } + return labelSizes; + } - /** + /** * Returns {width, height, offset} objects for the first, last, widest, highest tick * labels where offset indicates the anchor point offset from the top in pixels. * @return {{ first: object, last: object, widest: object, highest: object }} * @private */ - _computeLabelSizes() { - const me = this; - const ctx = me.ctx; - const caches = me._longestTextCache; - const sampleSize = me.options.ticks.sampleSize; - const widths = []; - const heights = []; - const offsets = []; - let widestLabelSize = 0; - let highestLabelSize = 0; - let ticks = me.ticks; - if (sampleSize < ticks.length) { - ticks = sample(ticks, sampleSize); - } - const length = ticks.length; - let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel; - - for (i = 0; i < length; ++i) { - label = ticks[i].label; - tickFont = me._resolveTickFontOptions(i); - ctx.font = fontString = tickFont.string; - cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; - lineHeight = tickFont.lineHeight; - width = height = 0; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(label) && !isArray(label)) { - width = _measureText(ctx, cache.data, cache.gc, width, label); - height = lineHeight; - } else if (isArray(label)) { - // if it is an array let's measure each element - for (j = 0, jlen = label.length; j < jlen; ++j) { - nestedLabel = label[j]; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { - width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel); - height += lineHeight; - } - } - } - widths.push(width); - heights.push(height); - offsets.push(lineHeight / 2); - widestLabelSize = Math.max(width, widestLabelSize); - highestLabelSize = Math.max(height, highestLabelSize); - } - garbageCollect(caches, length); - - const widest = widths.indexOf(widestLabelSize); - const highest = heights.indexOf(highestLabelSize); - - function valueAt(idx) { - return { - width: widths[idx] || 0, - height: heights[idx] || 0, - offset: offsets[idx] || 0 - }; - } - - return { - first: valueAt(0), - last: valueAt(length - 1), - widest: valueAt(widest), - highest: valueAt(highest) - }; - } - - /** + _computeLabelSizes() { + const me = this; + const ctx = me.ctx; + const caches = me._longestTextCache; + const sampleSize = me.options.ticks.sampleSize; + const widths = []; + const heights = []; + const offsets = []; + let widestLabelSize = 0; + let highestLabelSize = 0; + let ticks = me.ticks; + if (sampleSize < ticks.length) { + ticks = sample(ticks, sampleSize); + } + const length = ticks.length; + let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel; + + for (i = 0; i < length; ++i) { + label = ticks[i].label; + tickFont = me._resolveTickFontOptions(i); + ctx.font = fontString = tickFont.string; + cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; + lineHeight = tickFont.lineHeight; + width = height = 0; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(label) && !isArray(label)) { + width = _measureText(ctx, cache.data, cache.gc, width, label); + height = lineHeight; + } else if (isArray(label)) { + // if it is an array let's measure each element + for (j = 0, jlen = label.length; j < jlen; ++j) { + nestedLabel = label[j]; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { + width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel); + height += lineHeight; + } + } + } + widths.push(width); + heights.push(height); + offsets.push(lineHeight / 2); + widestLabelSize = Math.max(width, widestLabelSize); + highestLabelSize = Math.max(height, highestLabelSize); + } + garbageCollect(caches, length); + + const widest = widths.indexOf(widestLabelSize); + const highest = heights.indexOf(highestLabelSize); + + function valueAt(idx) { + return { + width: widths[idx] || 0, + height: heights[idx] || 0, + offset: offsets[idx] || 0 + }; + } + + return { + first: valueAt(0), + last: valueAt(length - 1), + widest: valueAt(widest), + highest: valueAt(highest) + }; + } + + /** * Used to get the label to display in the tooltip for the given value * @param {*} value * @return {string} */ - getLabelForValue(value) { - return value; - } + getLabelForValue(value) { + return value; + } - /** + /** * Returns the location of the given data point. Value can either be an index or a numerical value * The coordinate (0, 0) is at the upper-left corner of the canvas * @param {*} value * @param {number} [index] * @return {number} */ - getPixelForValue(value, index) { // eslint-disable-line no-unused-vars - return NaN; - } + getPixelForValue(value, index) { // eslint-disable-line no-unused-vars + return NaN; + } - /** + /** * Used to get the data value from a given pixel. This is the inverse of getPixelForValue * The coordinate (0, 0) is at the upper-left corner of the canvas * @param {number} pixel * @return {*} */ - getValueForPixel(pixel) {} // eslint-disable-line no-unused-vars + getValueForPixel(pixel) {} // eslint-disable-line no-unused-vars - /** + /** * Returns the location of the tick at the given index * The coordinate (0, 0) is at the upper-left corner of the canvas * @param {number} index * @return {number} */ - getPixelForTick(index) { - const ticks = this.ticks; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index].value); - } - - /** + getPixelForTick(index) { + const ticks = this.ticks; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index].value); + } + + /** * Utility for getting the pixel location of a percentage of scale * The coordinate (0, 0) is at the upper-left corner of the canvas * @param {number} decimal * @return {number} */ - getPixelForDecimal(decimal) { - const me = this; + getPixelForDecimal(decimal) { + const me = this; - if (me._reversePixels) { - decimal = 1 - decimal; - } + if (me._reversePixels) { + decimal = 1 - decimal; + } - return _int16Range(me._startPixel + decimal * me._length); - } + return _int16Range(me._startPixel + decimal * me._length); + } - /** + /** * @param {number} pixel * @return {number} */ - getDecimalForPixel(pixel) { - const decimal = (pixel - this._startPixel) / this._length; - return this._reversePixels ? 1 - decimal : decimal; - } + getDecimalForPixel(pixel) { + const decimal = (pixel - this._startPixel) / this._length; + return this._reversePixels ? 1 - decimal : decimal; + } - /** + /** * Returns the pixel for the minimum chart value * The coordinate (0, 0) is at the upper-left corner of the canvas * @return {number} */ - getBasePixel() { - return this.getPixelForValue(this.getBaseValue()); - } + getBasePixel() { + return this.getPixelForValue(this.getBaseValue()); + } - /** + /** * @return {number} */ - getBaseValue() { - const {min, max} = this; + getBaseValue() { + const {min, max} = this; - return min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0; - } + return min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + } - /** + /** * @protected */ - getContext(index) { - const me = this; - const ticks = me.ticks || []; + getContext(index) { + const me = this; + const ticks = me.ticks || []; - if (index >= 0 && index < ticks.length) { - const tick = ticks[index]; - return tick.$context || + if (index >= 0 && index < ticks.length) { + const tick = ticks[index]; + return tick.$context || (tick.$context = createTickContext(me.getContext(), index, tick)); - } - return me.$context || + } + return me.$context || (me.$context = createScaleContext(me.chart.getContext(), me)); - } + } - /** + /** * Returns a subset of ticks to be plotted to avoid overlapping labels. * @param {Tick[]} ticks * @return {Tick[]} * @private */ - _autoSkip(ticks) { - const me = this; - const tickOpts = me.options.ticks; - const ticksLimit = tickOpts.maxTicksLimit || me._length / me._tickSize(); - const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; - const numMajorIndices = majorIndices.length; - const first = majorIndices[0]; - const last = majorIndices[numMajorIndices - 1]; - const newTicks = []; - - // If there are too many major ticks to display them all - if (numMajorIndices > ticksLimit) { - skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit); - return newTicks; - } - - const spacing = calculateSpacing(majorIndices, ticks, ticksLimit); - - if (numMajorIndices > 0) { - let i, ilen; - const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null; - skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); - for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { - skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]); - } - skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); - return newTicks; - } - skip(ticks, newTicks, spacing); - return newTicks; - } - - /** + _autoSkip(ticks) { + const me = this; + const tickOpts = me.options.ticks; + const ticksLimit = tickOpts.maxTicksLimit || me._length / me._tickSize(); + const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; + const numMajorIndices = majorIndices.length; + const first = majorIndices[0]; + const last = majorIndices[numMajorIndices - 1]; + const newTicks = []; + + // If there are too many major ticks to display them all + if (numMajorIndices > ticksLimit) { + skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit); + return newTicks; + } + + const spacing = calculateSpacing(majorIndices, ticks, ticksLimit); + + if (numMajorIndices > 0) { + let i, ilen; + const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null; + skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); + for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { + skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]); + } + skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); + return newTicks; + } + skip(ticks, newTicks, spacing); + return newTicks; + } + + /** * @return {number} * @private */ - _tickSize() { - const me = this; - const optionTicks = me.options.ticks; - - // Calculate space needed by label in axis direction. - const rot = toRadians(me.labelRotation); - const cos = Math.abs(Math.cos(rot)); - const sin = Math.abs(Math.sin(rot)); - - const labelSizes = me._getLabelSizes(); - const padding = optionTicks.autoSkipPadding || 0; - const w = labelSizes ? labelSizes.widest.width + padding : 0; - const h = labelSizes ? labelSizes.highest.height + padding : 0; - - // Calculate space needed for 1 tick in axis direction. - return me.isHorizontal() - ? h * cos > w * sin ? w / cos : h / sin - : h * sin < w * cos ? h / cos : w / sin; - } - - /** + _tickSize() { + const me = this; + const optionTicks = me.options.ticks; + + // Calculate space needed by label in axis direction. + const rot = toRadians(me.labelRotation); + const cos = Math.abs(Math.cos(rot)); + const sin = Math.abs(Math.sin(rot)); + + const labelSizes = me._getLabelSizes(); + const padding = optionTicks.autoSkipPadding || 0; + const w = labelSizes ? labelSizes.widest.width + padding : 0; + const h = labelSizes ? labelSizes.highest.height + padding : 0; + + // Calculate space needed for 1 tick in axis direction. + return me.isHorizontal() + ? h * cos > w * sin ? w / cos : h / sin + : h * sin < w * cos ? h / cos : w / sin; + } + + /** * @return {boolean} * @private */ - _isVisible() { - const display = this.options.display; + _isVisible() { + const display = this.options.display; - if (display !== 'auto') { - return !!display; - } + if (display !== 'auto') { + return !!display; + } - return this.getMatchingVisibleMetas().length > 0; - } + return this.getMatchingVisibleMetas().length > 0; + } - /** + /** * @private */ - _computeGridLineItems(chartArea) { - const me = this; - const axis = me.axis; - const chart = me.chart; - const options = me.options; - const {gridLines, position} = options; - const offsetGridLines = gridLines.offsetGridLines; - const isHorizontal = me.isHorizontal(); - const ticks = me.ticks; - const ticksLength = ticks.length + (offsetGridLines ? 1 : 0); - const tl = getTickMarkLength(gridLines); - const items = []; - - let context = this.getContext(0); - const axisWidth = gridLines.drawBorder ? resolve([gridLines.borderWidth, gridLines.lineWidth, 0], context, 0) : 0; - const axisHalfWidth = axisWidth / 2; - const alignBorderValue = function(pixel) { - return _alignPixel(chart, pixel, axisWidth); - }; - let borderValue, i, lineValue, alignedLineValue; - let tx1, ty1, tx2, ty2, x1, y1, x2, y2; - - if (position === 'top') { - borderValue = alignBorderValue(me.bottom); - ty1 = me.bottom - tl; - ty2 = borderValue - axisHalfWidth; - y1 = alignBorderValue(chartArea.top) + axisHalfWidth; - y2 = chartArea.bottom; - } else if (position === 'bottom') { - borderValue = alignBorderValue(me.top); - y1 = chartArea.top; - y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; - ty1 = borderValue + axisHalfWidth; - ty2 = me.top + tl; - } else if (position === 'left') { - borderValue = alignBorderValue(me.right); - tx1 = me.right - tl; - tx2 = borderValue - axisHalfWidth; - x1 = alignBorderValue(chartArea.left) + axisHalfWidth; - x2 = chartArea.right; - } else if (position === 'right') { - borderValue = alignBorderValue(me.left); - x1 = chartArea.left; - x2 = alignBorderValue(chartArea.right) - axisHalfWidth; - tx1 = borderValue + axisHalfWidth; - tx2 = me.left + tl; - } else if (axis === 'x') { - if (position === 'center') { - borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2); - } else if (isObject(position)) { - const positionAxisID = Object.keys(position)[0]; - const value = position[positionAxisID]; - borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value)); - } - - y1 = chartArea.top; - y2 = chartArea.bottom; - ty1 = borderValue + axisHalfWidth; - ty2 = ty1 + tl; - } else if (axis === 'y') { - if (position === 'center') { - borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2); - } else if (isObject(position)) { - const positionAxisID = Object.keys(position)[0]; - const value = position[positionAxisID]; - borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value)); - } - - tx1 = borderValue - axisHalfWidth; - tx2 = tx1 - tl; - x1 = chartArea.left; - x2 = chartArea.right; - } - - for (i = 0; i < ticksLength; ++i) { - context = this.getContext(i); - - const lineWidth = resolve([gridLines.lineWidth], context, i); - const lineColor = resolve([gridLines.color], context, i); - const borderDash = gridLines.borderDash || []; - const borderDashOffset = resolve([gridLines.borderDashOffset], context, i); - - const tickWidth = resolve([gridLines.tickWidth, lineWidth], context, i); - const tickColor = resolve([gridLines.tickColor, lineColor], context, i); - const tickBorderDash = gridLines.tickBorderDash || borderDash; - const tickBorderDashOffset = resolve([gridLines.tickBorderDashOffset, borderDashOffset], context, i); - - lineValue = getPixelForGridLine(me, i, offsetGridLines); - - // Skip if the pixel is out of the range - if (lineValue === undefined) { - continue; - } - - alignedLineValue = _alignPixel(chart, lineValue, lineWidth); - - if (isHorizontal) { - tx1 = tx2 = x1 = x2 = alignedLineValue; - } else { - ty1 = ty2 = y1 = y2 = alignedLineValue; - } - - items.push({ - tx1, - ty1, - tx2, - ty2, - x1, - y1, - x2, - y2, - width: lineWidth, - color: lineColor, - borderDash, - borderDashOffset, - tickWidth, - tickColor, - tickBorderDash, - tickBorderDashOffset, - }); - } - - me._ticksLength = ticksLength; - me._borderValue = borderValue; - - return items; - } - - /** + _computeGridLineItems(chartArea) { + const me = this; + const axis = me.axis; + const chart = me.chart; + const options = me.options; + const {gridLines, position} = options; + const offsetGridLines = gridLines.offsetGridLines; + const isHorizontal = me.isHorizontal(); + const ticks = me.ticks; + const ticksLength = ticks.length + (offsetGridLines ? 1 : 0); + const tl = getTickMarkLength(gridLines); + const items = []; + + let context = this.getContext(0); + const axisWidth = gridLines.drawBorder ? resolve([gridLines.borderWidth, gridLines.lineWidth, 0], context, 0) : 0; + const axisHalfWidth = axisWidth / 2; + const alignBorderValue = function(pixel) { + return _alignPixel(chart, pixel, axisWidth); + }; + let borderValue, i, lineValue, alignedLineValue; + let tx1, ty1, tx2, ty2, x1, y1, x2, y2; + + if (position === 'top') { + borderValue = alignBorderValue(me.bottom); + ty1 = me.bottom - tl; + ty2 = borderValue - axisHalfWidth; + y1 = alignBorderValue(chartArea.top) + axisHalfWidth; + y2 = chartArea.bottom; + } else if (position === 'bottom') { + borderValue = alignBorderValue(me.top); + y1 = chartArea.top; + y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; + ty1 = borderValue + axisHalfWidth; + ty2 = me.top + tl; + } else if (position === 'left') { + borderValue = alignBorderValue(me.right); + tx1 = me.right - tl; + tx2 = borderValue - axisHalfWidth; + x1 = alignBorderValue(chartArea.left) + axisHalfWidth; + x2 = chartArea.right; + } else if (position === 'right') { + borderValue = alignBorderValue(me.left); + x1 = chartArea.left; + x2 = alignBorderValue(chartArea.right) - axisHalfWidth; + tx1 = borderValue + axisHalfWidth; + tx2 = me.left + tl; + } else if (axis === 'x') { + if (position === 'center') { + borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2); + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value)); + } + + y1 = chartArea.top; + y2 = chartArea.bottom; + ty1 = borderValue + axisHalfWidth; + ty2 = ty1 + tl; + } else if (axis === 'y') { + if (position === 'center') { + borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2); + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + borderValue = alignBorderValue(me.chart.scales[positionAxisID].getPixelForValue(value)); + } + + tx1 = borderValue - axisHalfWidth; + tx2 = tx1 - tl; + x1 = chartArea.left; + x2 = chartArea.right; + } + + for (i = 0; i < ticksLength; ++i) { + context = this.getContext(i); + + const lineWidth = resolve([gridLines.lineWidth], context, i); + const lineColor = resolve([gridLines.color], context, i); + const borderDash = gridLines.borderDash || []; + const borderDashOffset = resolve([gridLines.borderDashOffset], context, i); + + const tickWidth = resolve([gridLines.tickWidth, lineWidth], context, i); + const tickColor = resolve([gridLines.tickColor, lineColor], context, i); + const tickBorderDash = gridLines.tickBorderDash || borderDash; + const tickBorderDashOffset = resolve([gridLines.tickBorderDashOffset, borderDashOffset], context, i); + + lineValue = getPixelForGridLine(me, i, offsetGridLines); + + // Skip if the pixel is out of the range + if (lineValue === undefined) { + continue; + } + + alignedLineValue = _alignPixel(chart, lineValue, lineWidth); + + if (isHorizontal) { + tx1 = tx2 = x1 = x2 = alignedLineValue; + } else { + ty1 = ty2 = y1 = y2 = alignedLineValue; + } + + items.push({ + tx1, + ty1, + tx2, + ty2, + x1, + y1, + x2, + y2, + width: lineWidth, + color: lineColor, + borderDash, + borderDashOffset, + tickWidth, + tickColor, + tickBorderDash, + tickBorderDashOffset, + }); + } + + me._ticksLength = ticksLength; + me._borderValue = borderValue; + + return items; + } + + /** * @private */ - _computeLabelItems(chartArea) { - const me = this; - const axis = me.axis; - const options = me.options; - const {position, ticks: optionTicks} = options; - const isHorizontal = me.isHorizontal(); - const ticks = me.ticks; - const {align, crossAlign, padding} = optionTicks; - const tl = getTickMarkLength(options.gridLines); - const tickAndPadding = tl + padding; - const rotation = -toRadians(me.labelRotation); - const items = []; - let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; - let textBaseline = 'middle'; - - if (position === 'top') { - y = me.bottom - tickAndPadding; - textAlign = me._getXAxisLabelAlignment(); - } else if (position === 'bottom') { - y = me.top + tickAndPadding; - textAlign = me._getXAxisLabelAlignment(); - } else if (position === 'left') { - const ret = this._getYAxisLabelAlignment(tl); - textAlign = ret.textAlign; - x = ret.x; - } else if (position === 'right') { - const ret = this._getYAxisLabelAlignment(tl); - textAlign = ret.textAlign; - x = ret.x; - } else if (axis === 'x') { - if (position === 'center') { - y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding; - } else if (isObject(position)) { - const positionAxisID = Object.keys(position)[0]; - const value = position[positionAxisID]; - y = me.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding; - } - textAlign = me._getXAxisLabelAlignment(); - } else if (axis === 'y') { - if (position === 'center') { - x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding; - } else if (isObject(position)) { - const positionAxisID = Object.keys(position)[0]; - const value = position[positionAxisID]; - x = me.chart.scales[positionAxisID].getPixelForValue(value); - } - textAlign = this._getYAxisLabelAlignment(tl).textAlign; - } - - if (axis === 'y') { - if (align === 'start') { - textBaseline = 'top'; - } else if (align === 'end') { - textBaseline = 'bottom'; - } - } - - const labelSizes = me._getLabelSizes(); - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - tick = ticks[i]; - label = tick.label; - - pixel = me.getPixelForTick(i) + optionTicks.labelOffset; - font = me._resolveTickFontOptions(i); - lineHeight = font.lineHeight; - lineCount = isArray(label) ? label.length : 1; - const halfCount = lineCount / 2; - const color = resolve([optionTicks.color], me.getContext(i), i); - const strokeColor = resolve([optionTicks.textStrokeColor], me.getContext(i), i); - const strokeWidth = resolve([optionTicks.textStrokeWidth], me.getContext(i), i); - - if (isHorizontal) { - x = pixel; - if (position === 'top') { - if (crossAlign === 'near' || rotation !== 0) { - textOffset = (Math.sin(rotation) * halfCount + 0.5) * lineHeight; - textOffset -= (rotation === 0 ? (lineCount - 0.5) : Math.cos(rotation) * halfCount) * lineHeight; - } else if (crossAlign === 'center') { - textOffset = -1 * (labelSizes.highest.height / 2); - textOffset -= halfCount * lineHeight; - } else { - textOffset = (-1 * labelSizes.highest.height) + (0.5 * lineHeight); - } - } else { - // eslint-disable-next-line no-lonely-if - if (crossAlign === 'near' || rotation !== 0) { - textOffset = Math.sin(rotation) * halfCount * lineHeight; - textOffset += (rotation === 0 ? 0.5 : Math.cos(rotation) * halfCount) * lineHeight; - } else if (crossAlign === 'center') { - textOffset = labelSizes.highest.height / 2; - textOffset -= halfCount * lineHeight; - } else { - textOffset = labelSizes.highest.height - ((lineCount - 0.5) * lineHeight); - } - } - } else { - y = pixel; - textOffset = (1 - lineCount) * lineHeight / 2; - } - - items.push({ - rotation, - label, - font, - color, - strokeColor, - strokeWidth, - textOffset, - textAlign, - textBaseline, - translation: [x, y] - }); - } - - return items; - } - - _getXAxisLabelAlignment() { - const me = this; - const {position, ticks} = me.options; - const rotation = -toRadians(me.labelRotation); - - if (rotation) { - return position === 'top' ? 'left' : 'right'; - } - - let align = 'center'; - - if (ticks.align === 'start') { - align = 'left'; - } else if (ticks.align === 'end') { - align = 'right'; - } - - return align; - } - - _getYAxisLabelAlignment(tl) { - const me = this; - const {position, ticks} = me.options; - const {crossAlign, mirror, padding} = ticks; - const labelSizes = me._getLabelSizes(); - const tickAndPadding = tl + padding; - const widest = labelSizes.widest.width; - const lineSpace = labelSizes.highest.offset * 0.8; - - let textAlign; - let x; - - if (position === 'left') { - if (mirror) { - textAlign = 'left'; - x = me.right - padding; - } else { - x = me.right - tickAndPadding; - - if (crossAlign === 'near') { - textAlign = 'right'; - } else if (crossAlign === 'center') { - textAlign = 'center'; - x -= (widest / 2); - } else { - textAlign = 'left'; - x = me.left + lineSpace; - } - } - } else if (position === 'right') { - if (mirror) { - textAlign = 'right'; - x = me.left + padding; - } else { - x = me.left + tickAndPadding; - - if (crossAlign === 'near') { - textAlign = 'left'; - } else if (crossAlign === 'center') { - textAlign = 'center'; - x += widest / 2; - } else { - textAlign = 'right'; - x = me.right - lineSpace; - } - } - } else { - textAlign = 'right'; - } - - return {textAlign, x}; - } - - /** + _computeLabelItems(chartArea) { + const me = this; + const axis = me.axis; + const options = me.options; + const {position, ticks: optionTicks} = options; + const isHorizontal = me.isHorizontal(); + const ticks = me.ticks; + const {align, crossAlign, padding} = optionTicks; + const tl = getTickMarkLength(options.gridLines); + const tickAndPadding = tl + padding; + const rotation = -toRadians(me.labelRotation); + const items = []; + let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + let textBaseline = 'middle'; + + if (position === 'top') { + y = me.bottom - tickAndPadding; + textAlign = me._getXAxisLabelAlignment(); + } else if (position === 'bottom') { + y = me.top + tickAndPadding; + textAlign = me._getXAxisLabelAlignment(); + } else if (position === 'left') { + const ret = this._getYAxisLabelAlignment(tl); + textAlign = ret.textAlign; + x = ret.x; + } else if (position === 'right') { + const ret = this._getYAxisLabelAlignment(tl); + textAlign = ret.textAlign; + x = ret.x; + } else if (axis === 'x') { + if (position === 'center') { + y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding; + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + y = me.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding; + } + textAlign = me._getXAxisLabelAlignment(); + } else if (axis === 'y') { + if (position === 'center') { + x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding; + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + x = me.chart.scales[positionAxisID].getPixelForValue(value); + } + textAlign = this._getYAxisLabelAlignment(tl).textAlign; + } + + if (axis === 'y') { + if (align === 'start') { + textBaseline = 'top'; + } else if (align === 'end') { + textBaseline = 'bottom'; + } + } + + const labelSizes = me._getLabelSizes(); + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + tick = ticks[i]; + label = tick.label; + + pixel = me.getPixelForTick(i) + optionTicks.labelOffset; + font = me._resolveTickFontOptions(i); + lineHeight = font.lineHeight; + lineCount = isArray(label) ? label.length : 1; + const halfCount = lineCount / 2; + const color = resolve([optionTicks.color], me.getContext(i), i); + const strokeColor = resolve([optionTicks.textStrokeColor], me.getContext(i), i); + const strokeWidth = resolve([optionTicks.textStrokeWidth], me.getContext(i), i); + + if (isHorizontal) { + x = pixel; + if (position === 'top') { + if (crossAlign === 'near' || rotation !== 0) { + textOffset = (Math.sin(rotation) * halfCount + 0.5) * lineHeight; + textOffset -= (rotation === 0 ? (lineCount - 0.5) : Math.cos(rotation) * halfCount) * lineHeight; + } else if (crossAlign === 'center') { + textOffset = -1 * (labelSizes.highest.height / 2); + textOffset -= halfCount * lineHeight; + } else { + textOffset = (-1 * labelSizes.highest.height) + (0.5 * lineHeight); + } + } else { + // eslint-disable-next-line no-lonely-if + if (crossAlign === 'near' || rotation !== 0) { + textOffset = Math.sin(rotation) * halfCount * lineHeight; + textOffset += (rotation === 0 ? 0.5 : Math.cos(rotation) * halfCount) * lineHeight; + } else if (crossAlign === 'center') { + textOffset = labelSizes.highest.height / 2; + textOffset -= halfCount * lineHeight; + } else { + textOffset = labelSizes.highest.height - ((lineCount - 0.5) * lineHeight); + } + } + } else { + y = pixel; + textOffset = (1 - lineCount) * lineHeight / 2; + } + + items.push({ + rotation, + label, + font, + color, + strokeColor, + strokeWidth, + textOffset, + textAlign, + textBaseline, + translation: [x, y] + }); + } + + return items; + } + + _getXAxisLabelAlignment() { + const me = this; + const {position, ticks} = me.options; + const rotation = -toRadians(me.labelRotation); + + if (rotation) { + return position === 'top' ? 'left' : 'right'; + } + + let align = 'center'; + + if (ticks.align === 'start') { + align = 'left'; + } else if (ticks.align === 'end') { + align = 'right'; + } + + return align; + } + + _getYAxisLabelAlignment(tl) { + const me = this; + const {position, ticks} = me.options; + const {crossAlign, mirror, padding} = ticks; + const labelSizes = me._getLabelSizes(); + const tickAndPadding = tl + padding; + const widest = labelSizes.widest.width; + const lineSpace = labelSizes.highest.offset * 0.8; + + let textAlign; + let x; + + if (position === 'left') { + if (mirror) { + textAlign = 'left'; + x = me.right - padding; + } else { + x = me.right - tickAndPadding; + + if (crossAlign === 'near') { + textAlign = 'right'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x -= (widest / 2); + } else { + textAlign = 'left'; + x = me.left + lineSpace; + } + } + } else if (position === 'right') { + if (mirror) { + textAlign = 'right'; + x = me.left + padding; + } else { + x = me.left + tickAndPadding; + + if (crossAlign === 'near') { + textAlign = 'left'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x += widest / 2; + } else { + textAlign = 'right'; + x = me.right - lineSpace; + } + } + } else { + textAlign = 'right'; + } + + return {textAlign, x}; + } + + /** * @private */ - _computeLabelArea() { - const me = this; - const chart = me.chart; - const position = me.options.position; + _computeLabelArea() { + const me = this; + const chart = me.chart; + const position = me.options.position; - if (position === 'left' || position === 'right') { - return {top: 0, left: me.left, bottom: chart.height, right: me.right}; - } if (position === 'top' || position === 'bottom') { - return {top: me.top, left: 0, bottom: me.bottom, right: chart.width}; - } + if (position === 'left' || position === 'right') { + return {top: 0, left: me.left, bottom: chart.height, right: me.right}; + } if (position === 'top' || position === 'bottom') { + return {top: me.top, left: 0, bottom: me.bottom, right: chart.width}; + } - return null; - } + return null; + } - /** + /** * @protected */ - drawGrid(chartArea) { - const me = this; - const gridLines = me.options.gridLines; - const ctx = me.ctx; - const chart = me.chart; - let context = me.getContext(0); - const axisWidth = gridLines.drawBorder ? resolve([gridLines.borderWidth, gridLines.lineWidth, 0], context, 0) : 0; - const items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); - let i, ilen; - - if (gridLines.display) { - for (i = 0, ilen = items.length; i < ilen; ++i) { - const item = items[i]; - const {color, tickColor, tickWidth, width} = item; - - if (width && color && gridLines.drawOnChartArea) { - ctx.save(); - ctx.lineWidth = width; - ctx.strokeStyle = color; - if (ctx.setLineDash) { - ctx.setLineDash(item.borderDash); - ctx.lineDashOffset = item.borderDashOffset; - } - - ctx.beginPath(); - ctx.moveTo(item.x1, item.y1); - ctx.lineTo(item.x2, item.y2); - ctx.stroke(); - ctx.restore(); - } - - if (tickWidth && tickColor && gridLines.drawTicks) { - ctx.save(); - ctx.lineWidth = tickWidth; - ctx.strokeStyle = tickColor; - if (ctx.setLineDash) { - ctx.setLineDash(item.tickBorderDash); - ctx.lineDashOffset = item.tickBorderDashOffset; - } - - ctx.beginPath(); - ctx.moveTo(item.tx1, item.ty1); - ctx.lineTo(item.tx2, item.ty2); - ctx.stroke(); - ctx.restore(); - } - } - } - - if (axisWidth) { - // Draw the line at the edge of the axis - const firstLineWidth = axisWidth; - context = me.getContext(me._ticksLength - 1); - const lastLineWidth = resolve([gridLines.lineWidth, 1], context, me._ticksLength - 1); - const borderValue = me._borderValue; - let x1, x2, y1, y2; - - if (me.isHorizontal()) { - x1 = _alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; - x2 = _alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; - y1 = y2 = borderValue; - } else { - y1 = _alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; - y2 = _alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; - x1 = x2 = borderValue; - } - - ctx.lineWidth = axisWidth; - ctx.strokeStyle = resolve([gridLines.borderColor, gridLines.color], context, 0); - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - } - - /** + drawGrid(chartArea) { + const me = this; + const gridLines = me.options.gridLines; + const ctx = me.ctx; + const chart = me.chart; + let context = me.getContext(0); + const axisWidth = gridLines.drawBorder ? resolve([gridLines.borderWidth, gridLines.lineWidth, 0], context, 0) : 0; + const items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); + let i, ilen; + + if (gridLines.display) { + for (i = 0, ilen = items.length; i < ilen; ++i) { + const item = items[i]; + const {color, tickColor, tickWidth, width} = item; + + if (width && color && gridLines.drawOnChartArea) { + ctx.save(); + ctx.lineWidth = width; + ctx.strokeStyle = color; + if (ctx.setLineDash) { + ctx.setLineDash(item.borderDash); + ctx.lineDashOffset = item.borderDashOffset; + } + + ctx.beginPath(); + ctx.moveTo(item.x1, item.y1); + ctx.lineTo(item.x2, item.y2); + ctx.stroke(); + ctx.restore(); + } + + if (tickWidth && tickColor && gridLines.drawTicks) { + ctx.save(); + ctx.lineWidth = tickWidth; + ctx.strokeStyle = tickColor; + if (ctx.setLineDash) { + ctx.setLineDash(item.tickBorderDash); + ctx.lineDashOffset = item.tickBorderDashOffset; + } + + ctx.beginPath(); + ctx.moveTo(item.tx1, item.ty1); + ctx.lineTo(item.tx2, item.ty2); + ctx.stroke(); + ctx.restore(); + } + } + } + + if (axisWidth) { + // Draw the line at the edge of the axis + const firstLineWidth = axisWidth; + context = me.getContext(me._ticksLength - 1); + const lastLineWidth = resolve([gridLines.lineWidth, 1], context, me._ticksLength - 1); + const borderValue = me._borderValue; + let x1, x2, y1, y2; + + if (me.isHorizontal()) { + x1 = _alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; + x2 = _alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; + y1 = y2 = borderValue; + } else { + y1 = _alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; + y2 = _alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; + x1 = x2 = borderValue; + } + + ctx.lineWidth = axisWidth; + ctx.strokeStyle = resolve([gridLines.borderColor, gridLines.color], context, 0); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + } + + /** * @protected */ - drawLabels(chartArea) { - const me = this; - const optionTicks = me.options.ticks; - - if (!optionTicks.display) { - return; - } - - const ctx = me.ctx; - - const area = me._computeLabelArea(); - if (area) { - clipArea(ctx, area); - } - - const items = me._labelItems || (me._labelItems = me._computeLabelItems(chartArea)); - let i, ilen; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - const item = items[i]; - const tickFont = item.font; - const label = item.label; - let y = item.textOffset; - renderText(ctx, label, 0, y, tickFont, item); - } - - if (area) { - unclipArea(ctx); - } - } - - /** + drawLabels(chartArea) { + const me = this; + const optionTicks = me.options.ticks; + + if (!optionTicks.display) { + return; + } + + const ctx = me.ctx; + + const area = me._computeLabelArea(); + if (area) { + clipArea(ctx, area); + } + + const items = me._labelItems || (me._labelItems = me._computeLabelItems(chartArea)); + let i, ilen; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + const item = items[i]; + const tickFont = item.font; + const label = item.label; + let y = item.textOffset; + renderText(ctx, label, 0, y, tickFont, item); + } + + if (area) { + unclipArea(ctx); + } + } + + /** * @protected */ - drawTitle(chartArea) { // eslint-disable-line no-unused-vars - const me = this; - const ctx = me.ctx; - const options = me.options; - const scaleLabel = options.scaleLabel; - - if (!scaleLabel.display) { - return; - } - - const scaleLabelFont = toFont(scaleLabel.font, me.chart.options.font); - const scaleLabelPadding = toPadding(scaleLabel.padding); - const halfLineHeight = scaleLabelFont.lineHeight / 2; - const scaleLabelAlign = scaleLabel.align; - const position = options.position; - const isReverse = me.options.reverse; - let rotation = 0; - /** @type CanvasTextAlign */ - let textAlign; - let scaleLabelX, scaleLabelY; - - if (me.isHorizontal()) { - switch (scaleLabelAlign) { - case 'start': - scaleLabelX = me.left + (isReverse ? me.width : 0); - textAlign = isReverse ? 'right' : 'left'; - break; - case 'end': - scaleLabelX = me.left + (isReverse ? 0 : me.width); - textAlign = isReverse ? 'left' : 'right'; - break; - default: - scaleLabelX = me.left + me.width / 2; - textAlign = 'center'; - } - scaleLabelY = position === 'top' - ? me.top + halfLineHeight + scaleLabelPadding.top - : me.bottom - halfLineHeight - scaleLabelPadding.bottom; - } else { - const isLeft = position === 'left'; - scaleLabelX = isLeft - ? me.left + halfLineHeight + scaleLabelPadding.top - : me.right - halfLineHeight - scaleLabelPadding.top; - switch (scaleLabelAlign) { - case 'start': - scaleLabelY = me.top + (isReverse ? 0 : me.height); - textAlign = isReverse === isLeft ? 'right' : 'left'; - break; - case 'end': - scaleLabelY = me.top + (isReverse ? me.height : 0); - textAlign = isReverse === isLeft ? 'left' : 'right'; - break; - default: - scaleLabelY = me.top + me.height / 2; - textAlign = 'center'; - } - rotation = isLeft ? -HALF_PI : HALF_PI; - } - - renderText(ctx, scaleLabel.labelString, 0, 0, scaleLabelFont, { - color: scaleLabel.color, - rotation, - textAlign, - textBaseline: 'middle', - translation: [scaleLabelX, scaleLabelY], - }); - } - - draw(chartArea) { - const me = this; - - if (!me._isVisible()) { - return; - } - - me.drawGrid(chartArea); - me.drawTitle(); - me.drawLabels(chartArea); - } - - /** + drawTitle(chartArea) { // eslint-disable-line no-unused-vars + const me = this; + const ctx = me.ctx; + const options = me.options; + const scaleLabel = options.scaleLabel; + + if (!scaleLabel.display) { + return; + } + + const scaleLabelFont = toFont(scaleLabel.font, me.chart.options.font); + const scaleLabelPadding = toPadding(scaleLabel.padding); + const halfLineHeight = scaleLabelFont.lineHeight / 2; + const scaleLabelAlign = scaleLabel.align; + const position = options.position; + const isReverse = me.options.reverse; + let rotation = 0; + /** @type CanvasTextAlign */ + let textAlign; + let scaleLabelX, scaleLabelY; + + if (me.isHorizontal()) { + switch (scaleLabelAlign) { + case 'start': + scaleLabelX = me.left + (isReverse ? me.width : 0); + textAlign = isReverse ? 'right' : 'left'; + break; + case 'end': + scaleLabelX = me.left + (isReverse ? 0 : me.width); + textAlign = isReverse ? 'left' : 'right'; + break; + default: + scaleLabelX = me.left + me.width / 2; + textAlign = 'center'; + } + scaleLabelY = position === 'top' + ? me.top + halfLineHeight + scaleLabelPadding.top + : me.bottom - halfLineHeight - scaleLabelPadding.bottom; + } else { + const isLeft = position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + switch (scaleLabelAlign) { + case 'start': + scaleLabelY = me.top + (isReverse ? 0 : me.height); + textAlign = isReverse === isLeft ? 'right' : 'left'; + break; + case 'end': + scaleLabelY = me.top + (isReverse ? me.height : 0); + textAlign = isReverse === isLeft ? 'left' : 'right'; + break; + default: + scaleLabelY = me.top + me.height / 2; + textAlign = 'center'; + } + rotation = isLeft ? -HALF_PI : HALF_PI; + } + + renderText(ctx, scaleLabel.labelString, 0, 0, scaleLabelFont, { + color: scaleLabel.color, + rotation, + textAlign, + textBaseline: 'middle', + translation: [scaleLabelX, scaleLabelY], + }); + } + + draw(chartArea) { + const me = this; + + if (!me._isVisible()) { + return; + } + + me.drawGrid(chartArea); + me.drawTitle(); + me.drawLabels(chartArea); + } + + /** * @return {object[]} * @private */ - _layers() { - const me = this; - const opts = me.options; - const tz = opts.ticks && opts.ticks.z || 0; - const gz = opts.gridLines && opts.gridLines.z || 0; - - if (!me._isVisible() || tz === gz || me.draw !== me._draw) { - // backward compatibility: draw has been overridden by custom scale - return [{ - z: tz, - draw(chartArea) { - me.draw(chartArea); - } - }]; - } - - return [{ - z: gz, - draw(chartArea) { - me.drawGrid(chartArea); - me.drawTitle(); - } - }, { - z: tz, - draw(chartArea) { - me.drawLabels(chartArea); - } - }]; - } - - /** + _layers() { + const me = this; + const opts = me.options; + const tz = opts.ticks && opts.ticks.z || 0; + const gz = opts.gridLines && opts.gridLines.z || 0; + + if (!me._isVisible() || tz === gz || me.draw !== me._draw) { + // backward compatibility: draw has been overridden by custom scale + return [{ + z: tz, + draw(chartArea) { + me.draw(chartArea); + } + }]; + } + + return [{ + z: gz, + draw(chartArea) { + me.drawGrid(chartArea); + me.drawTitle(); + } + }, { + z: tz, + draw(chartArea) { + me.drawLabels(chartArea); + } + }]; + } + + /** * Returns visible dataset metas that are attached to this scale * @param {string} [type] - if specified, also filter by dataset type * @return {object[]} */ - getMatchingVisibleMetas(type) { - const me = this; - const metas = me.chart.getSortedVisibleDatasetMetas(); - const axisID = me.axis + 'AxisID'; - const result = []; - let i, ilen; - - for (i = 0, ilen = metas.length; i < ilen; ++i) { - const meta = metas[i]; - if (meta[axisID] === me.id && (!type || meta.type === type)) { - result.push(meta); - } - } - return result; - } - - /** + getMatchingVisibleMetas(type) { + const me = this; + const metas = me.chart.getSortedVisibleDatasetMetas(); + const axisID = me.axis + 'AxisID'; + const result = []; + let i, ilen; + + for (i = 0, ilen = metas.length; i < ilen; ++i) { + const meta = metas[i]; + if (meta[axisID] === me.id && (!type || meta.type === type)) { + result.push(meta); + } + } + return result; + } + + /** * @param {number} index * @return {object} * @protected */ - _resolveTickFontOptions(index) { - const me = this; - const chart = me.chart; - const options = me.options.ticks; - const context = me.getContext(index); - return toFont(resolve([options.font], context), chart.options.font); - } + _resolveTickFontOptions(index) { + const me = this; + const chart = me.chart; + const options = me.options.ticks; + const context = me.getContext(index); + return toFont(resolve([options.font], context), chart.options.font); + } } Scale.prototype._draw = Scale.prototype.draw; diff --git a/src/core/core.ticks.js b/src/core/core.ticks.js index 1e6770d643d..8e4086574c6 100644 --- a/src/core/core.ticks.js +++ b/src/core/core.ticks.js @@ -7,17 +7,17 @@ import {formatNumber} from './core.intl'; * @namespace Chart.Ticks.formatters */ const formatters = { - /** + /** * Formatter for value labels * @method Chart.Ticks.formatters.values * @param value the value to display * @return {string|string[]} the label to display */ - values(value) { - return isArray(value) ? value : '' + value; - }, + values(value) { + return isArray(value) ? value : '' + value; + }, - /** + /** * Formatter for numeric ticks * @method Chart.Ticks.formatters.numeric * @param tickValue {number} the value to be formatted @@ -25,38 +25,38 @@ const formatters = { * @param ticks {object[]} the list of ticks being converted * @return {string} string representation of the tickValue parameter */ - numeric(tickValue, index, ticks) { - if (tickValue === 0) { - return '0'; // never show decimal places for 0 - } + numeric(tickValue, index, ticks) { + if (tickValue === 0) { + return '0'; // never show decimal places for 0 + } - const locale = this.chart.options.locale; + const locale = this.chart.options.locale; - // all ticks are small or there huge numbers; use scientific notation - const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value)); - let notation; - if (maxTick < 1e-4 || maxTick > 1e+15) { - notation = 'scientific'; - } + // all ticks are small or there huge numbers; use scientific notation + const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value)); + let notation; + if (maxTick < 1e-4 || maxTick > 1e+15) { + notation = 'scientific'; + } - // Figure out how many digits to show - // The space between the first two ticks might be smaller than normal spacing - let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value; + // Figure out how many digits to show + // The space between the first two ticks might be smaller than normal spacing + let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value; - // If we have a number like 2.5 as the delta, figure out how many decimal places we need - if (Math.abs(delta) > 1 && tickValue !== Math.floor(tickValue)) { - // not an integer - delta = tickValue - Math.floor(tickValue); - } + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1 && tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } - const logDelta = log10(Math.abs(delta)); - const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0); // toFixed has a max of 20 decimal places + const logDelta = log10(Math.abs(delta)); + const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0); // toFixed has a max of 20 decimal places - const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal}; - Object.assign(options, this.options.ticks.format); + const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal}; + Object.assign(options, this.options.ticks.format); - return formatNumber(tickValue, locale, options); - } + return formatNumber(tickValue, locale, options); + } }; /** @@ -68,14 +68,14 @@ const formatters = { * @return {string} string representation of the tickValue parameter */ formatters.logarithmic = function(tickValue, index, ticks) { - if (tickValue === 0) { - return '0'; - } - const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue)))); - if (remain === 1 || remain === 2 || remain === 5) { - return formatters.numeric.call(this, tickValue, index, ticks); - } - return ''; + if (tickValue === 0) { + return '0'; + } + const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue)))); + if (remain === 1 || remain === 2 || remain === 5) { + return formatters.numeric.call(this, tickValue, index, ticks); + } + return ''; }; /** diff --git a/src/core/core.typedRegistry.js b/src/core/core.typedRegistry.js index 0f80b2df42b..45df85a1d17 100644 --- a/src/core/core.typedRegistry.js +++ b/src/core/core.typedRegistry.js @@ -5,103 +5,103 @@ import defaults from './core.defaults'; */ export default class TypedRegistry { - constructor(type, scope) { - this.type = type; - this.scope = scope; - this.items = Object.create(null); - } + constructor(type, scope) { + this.type = type; + this.scope = scope; + this.items = Object.create(null); + } - isForType(type) { - return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype); - } + isForType(type) { + return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype); + } - /** + /** * @param {IChartComponent} item * @returns {string} The scope where items defaults were registered to. */ - register(item) { - const proto = Object.getPrototypeOf(item); - let parentScope; + register(item) { + const proto = Object.getPrototypeOf(item); + let parentScope; - if (isIChartComponent(proto)) { - // Make sure the parent is registered and note the scope where its defaults are. - parentScope = this.register(proto); - } + if (isIChartComponent(proto)) { + // Make sure the parent is registered and note the scope where its defaults are. + parentScope = this.register(proto); + } - const items = this.items; - const id = item.id; - const baseScope = this.scope; - const scope = baseScope ? baseScope + '.' + id : id; + const items = this.items; + const id = item.id; + const baseScope = this.scope; + const scope = baseScope ? baseScope + '.' + id : id; - if (!id) { - throw new Error('class does not have id: ' + item); - } + if (!id) { + throw new Error('class does not have id: ' + item); + } - if (id in items) { - // already registered - return scope; - } + if (id in items) { + // already registered + return scope; + } - items[id] = item; - registerDefaults(item, scope, parentScope); + items[id] = item; + registerDefaults(item, scope, parentScope); - return scope; - } + return scope; + } - /** + /** * @param {string} id * @returns {object?} */ - get(id) { - return this.items[id]; - } + get(id) { + return this.items[id]; + } - /** + /** * @param {IChartComponent} item */ - unregister(item) { - const items = this.items; - const id = item.id; - const scope = this.scope; - - if (id in items) { - delete items[id]; - } - - if (scope && id in defaults[scope]) { - delete defaults[scope][id]; - } - } + unregister(item) { + const items = this.items; + const id = item.id; + const scope = this.scope; + + if (id in items) { + delete items[id]; + } + + if (scope && id in defaults[scope]) { + delete defaults[scope][id]; + } + } } function registerDefaults(item, scope, parentScope) { - // Inherit the parent's defaults and keep existing defaults - const itemDefaults = Object.assign( - Object.create(null), - parentScope && defaults.get(parentScope), - item.defaults, - defaults.get(scope) - ); - - defaults.set(scope, itemDefaults); - - if (item.defaultRoutes) { - routeDefaults(scope, item.defaultRoutes); - } + // Inherit the parent's defaults and keep existing defaults + const itemDefaults = Object.assign( + Object.create(null), + parentScope && defaults.get(parentScope), + item.defaults, + defaults.get(scope) + ); + + defaults.set(scope, itemDefaults); + + if (item.defaultRoutes) { + routeDefaults(scope, item.defaultRoutes); + } } function routeDefaults(scope, routes) { - Object.keys(routes).forEach(property => { - const propertyParts = property.split('.'); - const sourceName = propertyParts.pop(); - const sourceScope = [scope].concat(propertyParts).join('.'); - const parts = routes[property].split('.'); - const targetName = parts.pop(); - const targetScope = parts.join('.'); - defaults.route(sourceScope, sourceName, targetScope, targetName); - }); + Object.keys(routes).forEach(property => { + const propertyParts = property.split('.'); + const sourceName = propertyParts.pop(); + const sourceScope = [scope].concat(propertyParts).join('.'); + const parts = routes[property].split('.'); + const targetName = parts.pop(); + const targetScope = parts.join('.'); + defaults.route(sourceScope, sourceName, targetScope, targetName); + }); } function isIChartComponent(proto) { - return 'id' in proto && 'defaults' in proto; + return 'id' in proto && 'defaults' in proto; } diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 7769353138f..286008c3476 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -2,207 +2,207 @@ import Element from '../core/core.element'; import {_angleBetween, getAngleFromPoint, TAU, HALF_PI} from '../helpers/index'; function clipArc(ctx, element) { - const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element; - let angleMargin = pixelMargin / outerRadius; - - // Draw an inner border by clipping the arc and drawing a double-width border - // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders - ctx.beginPath(); - ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin); - if (innerRadius > pixelMargin) { - angleMargin = pixelMargin / innerRadius; - ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true); - } else { - ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI); - } - ctx.closePath(); - ctx.clip(); + const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element; + let angleMargin = pixelMargin / outerRadius; + + // Draw an inner border by clipping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (innerRadius > pixelMargin) { + angleMargin = pixelMargin / innerRadius; + ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI); + } + ctx.closePath(); + ctx.clip(); } function pathArc(ctx, element) { - const {x, y, startAngle, endAngle, pixelMargin} = element; - const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); - const innerRadius = element.innerRadius + pixelMargin; - - ctx.beginPath(); - ctx.arc(x, y, outerRadius, startAngle, endAngle); - ctx.arc(x, y, innerRadius, endAngle, startAngle, true); - ctx.closePath(); + const {x, y, startAngle, endAngle, pixelMargin} = element; + const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); + const innerRadius = element.innerRadius + pixelMargin; + + ctx.beginPath(); + ctx.arc(x, y, outerRadius, startAngle, endAngle); + ctx.arc(x, y, innerRadius, endAngle, startAngle, true); + ctx.closePath(); } function drawArc(ctx, element) { - if (element.fullCircles) { - element.endAngle = element.startAngle + TAU; + if (element.fullCircles) { + element.endAngle = element.startAngle + TAU; - pathArc(ctx, element); + pathArc(ctx, element); - for (let i = 0; i < element.fullCircles; ++i) { - ctx.fill(); - } - element.endAngle = element.startAngle + element.circumference % TAU; - } + for (let i = 0; i < element.fullCircles; ++i) { + ctx.fill(); + } + element.endAngle = element.startAngle + element.circumference % TAU; + } - pathArc(ctx, element); - ctx.fill(); + pathArc(ctx, element); + ctx.fill(); } function drawFullCircleBorders(ctx, element, inner) { - const {x, y, startAngle, endAngle, pixelMargin} = element; - const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); - const innerRadius = element.innerRadius + pixelMargin; - - let i; - - if (inner) { - element.endAngle = element.startAngle + TAU; - clipArc(ctx, element); - element.endAngle = endAngle; - if (element.endAngle === element.startAngle) { - element.endAngle += TAU; - element.fullCircles--; - } - } - - ctx.beginPath(); - ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true); - for (i = 0; i < element.fullCircles; ++i) { - ctx.stroke(); - } - - ctx.beginPath(); - ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU); - for (i = 0; i < element.fullCircles; ++i) { - ctx.stroke(); - } + const {x, y, startAngle, endAngle, pixelMargin} = element; + const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); + const innerRadius = element.innerRadius + pixelMargin; + + let i; + + if (inner) { + element.endAngle = element.startAngle + TAU; + clipArc(ctx, element); + element.endAngle = endAngle; + if (element.endAngle === element.startAngle) { + element.endAngle += TAU; + element.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true); + for (i = 0; i < element.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU); + for (i = 0; i < element.fullCircles; ++i) { + ctx.stroke(); + } } function drawBorder(ctx, element) { - const {x, y, startAngle, endAngle, pixelMargin, options} = element; - const outerRadius = element.outerRadius; - const innerRadius = element.innerRadius + pixelMargin; - const inner = options.borderAlign === 'inner'; - - if (!options.borderWidth) { - return; - } - - if (inner) { - ctx.lineWidth = options.borderWidth * 2; - ctx.lineJoin = 'round'; - } else { - ctx.lineWidth = options.borderWidth; - ctx.lineJoin = 'bevel'; - } - - if (element.fullCircles) { - drawFullCircleBorders(ctx, element, inner); - } - - if (inner) { - clipArc(ctx, element); - } - - ctx.beginPath(); - ctx.arc(x, y, outerRadius, startAngle, endAngle); - ctx.arc(x, y, innerRadius, endAngle, startAngle, true); - ctx.closePath(); - ctx.stroke(); + const {x, y, startAngle, endAngle, pixelMargin, options} = element; + const outerRadius = element.outerRadius; + const innerRadius = element.innerRadius + pixelMargin; + const inner = options.borderAlign === 'inner'; + + if (!options.borderWidth) { + return; + } + + if (inner) { + ctx.lineWidth = options.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = options.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (element.fullCircles) { + drawFullCircleBorders(ctx, element, inner); + } + + if (inner) { + clipArc(ctx, element); + } + + ctx.beginPath(); + ctx.arc(x, y, outerRadius, startAngle, endAngle); + ctx.arc(x, y, innerRadius, endAngle, startAngle, true); + ctx.closePath(); + ctx.stroke(); } export default class ArcElement extends Element { - constructor(cfg) { - super(); + constructor(cfg) { + super(); - this.options = undefined; - this.circumference = undefined; - this.startAngle = undefined; - this.endAngle = undefined; - this.innerRadius = undefined; - this.outerRadius = undefined; - this.pixelMargin = 0; - this.fullCircles = 0; + this.options = undefined; + this.circumference = undefined; + this.startAngle = undefined; + this.endAngle = undefined; + this.innerRadius = undefined; + this.outerRadius = undefined; + this.pixelMargin = 0; + this.fullCircles = 0; - if (cfg) { - Object.assign(this, cfg); - } - } + if (cfg) { + Object.assign(this, cfg); + } + } - /** + /** * @param {number} chartX * @param {number} chartY * @param {boolean} [useFinalPosition] */ - inRange(chartX, chartY, useFinalPosition) { - const point = this.getProps(['x', 'y'], useFinalPosition); - const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY}); - const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([ - 'startAngle', - 'endAngle', - 'innerRadius', - 'outerRadius', - 'circumference' - ], useFinalPosition); - const betweenAngles = circumference >= TAU || _angleBetween(angle, startAngle, endAngle); - const withinRadius = (distance >= innerRadius && distance <= outerRadius); - - return (betweenAngles && withinRadius); - } - - /** + inRange(chartX, chartY, useFinalPosition) { + const point = this.getProps(['x', 'y'], useFinalPosition); + const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY}); + const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([ + 'startAngle', + 'endAngle', + 'innerRadius', + 'outerRadius', + 'circumference' + ], useFinalPosition); + const betweenAngles = circumference >= TAU || _angleBetween(angle, startAngle, endAngle); + const withinRadius = (distance >= innerRadius && distance <= outerRadius); + + return (betweenAngles && withinRadius); + } + + /** * @param {boolean} [useFinalPosition] */ - getCenterPoint(useFinalPosition) { - const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([ - 'x', - 'y', - 'startAngle', - 'endAngle', - 'innerRadius', - 'outerRadius' - ], useFinalPosition); - const halfAngle = (startAngle + endAngle) / 2; - const halfRadius = (innerRadius + outerRadius) / 2; - return { - x: x + Math.cos(halfAngle) * halfRadius, - y: y + Math.sin(halfAngle) * halfRadius - }; - } - - /** + getCenterPoint(useFinalPosition) { + const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([ + 'x', + 'y', + 'startAngle', + 'endAngle', + 'innerRadius', + 'outerRadius' + ], useFinalPosition); + const halfAngle = (startAngle + endAngle) / 2; + const halfRadius = (innerRadius + outerRadius) / 2; + return { + x: x + Math.cos(halfAngle) * halfRadius, + y: y + Math.sin(halfAngle) * halfRadius + }; + } + + /** * @param {boolean} [useFinalPosition] */ - tooltipPosition(useFinalPosition) { - return this.getCenterPoint(useFinalPosition); - } + tooltipPosition(useFinalPosition) { + return this.getCenterPoint(useFinalPosition); + } - draw(ctx) { - const me = this; - const options = me.options; - const offset = options.offset || 0; - me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0; - me.fullCircles = Math.floor(me.circumference / TAU); + draw(ctx) { + const me = this; + const options = me.options; + const offset = options.offset || 0; + me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0; + me.fullCircles = Math.floor(me.circumference / TAU); - if (me.circumference === 0 || me.innerRadius < 0 || me.outerRadius < 0) { - return; - } + if (me.circumference === 0 || me.innerRadius < 0 || me.outerRadius < 0) { + return; + } - ctx.save(); + ctx.save(); - if (offset && me.circumference < TAU) { - const halfAngle = (me.startAngle + me.endAngle) / 2; - ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset); - } + if (offset && me.circumference < TAU) { + const halfAngle = (me.startAngle + me.endAngle) / 2; + ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset); + } - ctx.fillStyle = options.backgroundColor; - ctx.strokeStyle = options.borderColor; + ctx.fillStyle = options.backgroundColor; + ctx.strokeStyle = options.borderColor; - drawArc(ctx, me); - drawBorder(ctx, me); + drawArc(ctx, me); + drawBorder(ctx, me); - ctx.restore(); - } + ctx.restore(); + } } ArcElement.id = 'arc'; @@ -211,15 +211,15 @@ ArcElement.id = 'arc'; * @type {any} */ ArcElement.defaults = { - borderAlign: 'center', - borderColor: '#fff', - borderWidth: 2, - offset: 0 + borderAlign: 'center', + borderColor: '#fff', + borderWidth: 2, + offset: 0 }; /** * @type {any} */ ArcElement.defaultRoutes = { - backgroundColor: 'backgroundColor' + backgroundColor: 'backgroundColor' }; diff --git a/src/elements/element.bar.js b/src/elements/element.bar.js index 498fdfea716..c12b7a45324 100644 --- a/src/elements/element.bar.js +++ b/src/elements/element.bar.js @@ -10,135 +10,135 @@ import {PI, HALF_PI} from '../helpers/helpers.math'; * @private */ function getBarBounds(bar, useFinalPosition) { - const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition); - - let left, right, top, bottom, half; - - if (bar.horizontal) { - half = height / 2; - left = Math.min(x, base); - right = Math.max(x, base); - top = y - half; - bottom = y + half; - } else { - half = width / 2; - left = x - half; - right = x + half; - top = Math.min(y, base); - bottom = Math.max(y, base); - } - - return {left, top, right, bottom}; + const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition); + + let left, right, top, bottom, half; + + if (bar.horizontal) { + half = height / 2; + left = Math.min(x, base); + right = Math.max(x, base); + top = y - half; + bottom = y + half; + } else { + half = width / 2; + left = x - half; + right = x + half; + top = Math.min(y, base); + bottom = Math.max(y, base); + } + + return {left, top, right, bottom}; } function parseBorderSkipped(bar) { - let edge = bar.options.borderSkipped; - const res = {}; + let edge = bar.options.borderSkipped; + const res = {}; - if (!edge) { - return res; - } + if (!edge) { + return res; + } - edge = bar.horizontal - ? parseEdge(edge, 'left', 'right', bar.base > bar.x) - : parseEdge(edge, 'bottom', 'top', bar.base < bar.y); + edge = bar.horizontal + ? parseEdge(edge, 'left', 'right', bar.base > bar.x) + : parseEdge(edge, 'bottom', 'top', bar.base < bar.y); - res[edge] = true; - return res; + res[edge] = true; + return res; } function parseEdge(edge, a, b, reverse) { - if (reverse) { - edge = swap(edge, a, b); - edge = startEnd(edge, b, a); - } else { - edge = startEnd(edge, a, b); - } - return edge; + if (reverse) { + edge = swap(edge, a, b); + edge = startEnd(edge, b, a); + } else { + edge = startEnd(edge, a, b); + } + return edge; } function swap(orig, v1, v2) { - return orig === v1 ? v2 : orig === v2 ? v1 : orig; + return orig === v1 ? v2 : orig === v2 ? v1 : orig; } function startEnd(v, start, end) { - return v === 'start' ? start : v === 'end' ? end : v; + return v === 'start' ? start : v === 'end' ? end : v; } function skipOrLimit(skip, value, min, max) { - return skip ? 0 : Math.max(Math.min(value, max), min); + return skip ? 0 : Math.max(Math.min(value, max), min); } function parseBorderWidth(bar, maxW, maxH) { - const value = bar.options.borderWidth; - const skip = parseBorderSkipped(bar); - const o = toTRBL(value); - - return { - t: skipOrLimit(skip.top, o.top, 0, maxH), - r: skipOrLimit(skip.right, o.right, 0, maxW), - b: skipOrLimit(skip.bottom, o.bottom, 0, maxH), - l: skipOrLimit(skip.left, o.left, 0, maxW) - }; + const value = bar.options.borderWidth; + const skip = parseBorderSkipped(bar); + const o = toTRBL(value); + + return { + t: skipOrLimit(skip.top, o.top, 0, maxH), + r: skipOrLimit(skip.right, o.right, 0, maxW), + b: skipOrLimit(skip.bottom, o.bottom, 0, maxH), + l: skipOrLimit(skip.left, o.left, 0, maxW) + }; } function parseBorderRadius(bar, maxW, maxH) { - const value = bar.options.borderRadius; - const o = toTRBLCorners(value); - const maxR = Math.min(maxW, maxH); - const skip = parseBorderSkipped(bar); - - return { - topLeft: skipOrLimit(skip.top || skip.left, o.topLeft, 0, maxR), - topRight: skipOrLimit(skip.top || skip.right, o.topRight, 0, maxR), - bottomLeft: skipOrLimit(skip.bottom || skip.left, o.bottomLeft, 0, maxR), - bottomRight: skipOrLimit(skip.bottom || skip.right, o.bottomRight, 0, maxR) - }; + const value = bar.options.borderRadius; + const o = toTRBLCorners(value); + const maxR = Math.min(maxW, maxH); + const skip = parseBorderSkipped(bar); + + return { + topLeft: skipOrLimit(skip.top || skip.left, o.topLeft, 0, maxR), + topRight: skipOrLimit(skip.top || skip.right, o.topRight, 0, maxR), + bottomLeft: skipOrLimit(skip.bottom || skip.left, o.bottomLeft, 0, maxR), + bottomRight: skipOrLimit(skip.bottom || skip.right, o.bottomRight, 0, maxR) + }; } function boundingRects(bar) { - const bounds = getBarBounds(bar); - const width = bounds.right - bounds.left; - const height = bounds.bottom - bounds.top; - const border = parseBorderWidth(bar, width / 2, height / 2); - const radius = parseBorderRadius(bar, width / 2, height / 2); - - return { - outer: { - x: bounds.left, - y: bounds.top, - w: width, - h: height, - radius - }, - inner: { - x: bounds.left + border.l, - y: bounds.top + border.t, - w: width - border.l - border.r, - h: height - border.t - border.b, - radius: { - topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)), - topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)), - bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)), - bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)), - } - } - }; + const bounds = getBarBounds(bar); + const width = bounds.right - bounds.left; + const height = bounds.bottom - bounds.top; + const border = parseBorderWidth(bar, width / 2, height / 2); + const radius = parseBorderRadius(bar, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height, + radius + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b, + radius: { + topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)), + topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)), + bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)), + bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)), + } + } + }; } function inRange(bar, x, y, useFinalPosition) { - const skipX = x === null; - const skipY = y === null; - const skipBoth = skipX && skipY; - const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition); + const skipX = x === null; + const skipY = y === null; + const skipBoth = skipX && skipY; + const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition); - return bounds + return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom); } function hasRadius(radius) { - return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight; + return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight; } /** @@ -147,31 +147,31 @@ function hasRadius(radius) { * @param {*} rect Bounding rect */ function addRoundedRectPath(ctx, rect) { - const {x, y, w, h, radius} = rect; + const {x, y, w, h, radius} = rect; - // top left arc - ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true); + // top left arc + ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true); - // line from top left to bottom left - ctx.lineTo(x, y + h - radius.bottomLeft); + // line from top left to bottom left + ctx.lineTo(x, y + h - radius.bottomLeft); - // bottom left arc - ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true); + // bottom left arc + ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true); - // line from bottom left to bottom right - ctx.lineTo(x + w - radius.bottomRight, y + h); + // line from bottom left to bottom right + ctx.lineTo(x + w - radius.bottomRight, y + h); - // bottom right arc - ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true); + // bottom right arc + ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true); - // line from bottom right to top right - ctx.lineTo(x + w, y + radius.topRight); + // line from bottom right to top right + ctx.lineTo(x + w, y + radius.topRight); - // top right arc - ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true); + // top right arc + ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true); - // line from top right to top left - ctx.lineTo(x + radius.topLeft, y); + // line from top right to top left + ctx.lineTo(x + radius.topLeft, y); } /** @@ -180,72 +180,72 @@ function addRoundedRectPath(ctx, rect) { * @param {*} rect Bounding rect */ function addNormalRectPath(ctx, rect) { - ctx.rect(rect.x, rect.y, rect.w, rect.h); + ctx.rect(rect.x, rect.y, rect.w, rect.h); } export default class BarElement extends Element { - constructor(cfg) { - super(); - - this.options = undefined; - this.horizontal = undefined; - this.base = undefined; - this.width = undefined; - this.height = undefined; - - if (cfg) { - Object.assign(this, cfg); - } - } - - draw(ctx) { - const options = this.options; - const {inner, outer} = boundingRects(this); - const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath; - - ctx.save(); - - if (outer.w !== inner.w || outer.h !== inner.h) { - ctx.beginPath(); - addRectPath(ctx, outer); - ctx.clip(); - addRectPath(ctx, inner); - ctx.fillStyle = options.borderColor; - ctx.fill('evenodd'); - } - - ctx.beginPath(); - addRectPath(ctx, inner); - ctx.fillStyle = options.backgroundColor; - ctx.fill(); - - ctx.restore(); - } - - inRange(mouseX, mouseY, useFinalPosition) { - return inRange(this, mouseX, mouseY, useFinalPosition); - } - - inXRange(mouseX, useFinalPosition) { - return inRange(this, mouseX, null, useFinalPosition); - } - - inYRange(mouseY, useFinalPosition) { - return inRange(this, null, mouseY, useFinalPosition); - } - - getCenterPoint(useFinalPosition) { - const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition); - return { - x: horizontal ? (x + base) / 2 : x, - y: horizontal ? y : (y + base) / 2 - }; - } - - getRange(axis) { - return axis === 'x' ? this.width / 2 : this.height / 2; - } + constructor(cfg) { + super(); + + this.options = undefined; + this.horizontal = undefined; + this.base = undefined; + this.width = undefined; + this.height = undefined; + + if (cfg) { + Object.assign(this, cfg); + } + } + + draw(ctx) { + const options = this.options; + const {inner, outer} = boundingRects(this); + const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath; + + ctx.save(); + + if (outer.w !== inner.w || outer.h !== inner.h) { + ctx.beginPath(); + addRectPath(ctx, outer); + ctx.clip(); + addRectPath(ctx, inner); + ctx.fillStyle = options.borderColor; + ctx.fill('evenodd'); + } + + ctx.beginPath(); + addRectPath(ctx, inner); + ctx.fillStyle = options.backgroundColor; + ctx.fill(); + + ctx.restore(); + } + + inRange(mouseX, mouseY, useFinalPosition) { + return inRange(this, mouseX, mouseY, useFinalPosition); + } + + inXRange(mouseX, useFinalPosition) { + return inRange(this, mouseX, null, useFinalPosition); + } + + inYRange(mouseY, useFinalPosition) { + return inRange(this, null, mouseY, useFinalPosition); + } + + getCenterPoint(useFinalPosition) { + const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition); + return { + x: horizontal ? (x + base) / 2 : x, + y: horizontal ? y : (y + base) / 2 + }; + } + + getRange(axis) { + return axis === 'x' ? this.width / 2 : this.height / 2; + } } BarElement.id = 'bar'; @@ -254,15 +254,15 @@ BarElement.id = 'bar'; * @type {any} */ BarElement.defaults = { - borderSkipped: 'start', - borderWidth: 0, - borderRadius: 0 + borderSkipped: 'start', + borderWidth: 0, + borderRadius: 0 }; /** * @type {any} */ BarElement.defaultRoutes = { - backgroundColor: 'backgroundColor', - borderColor: 'borderColor' + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' }; diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 9379bae53f7..9aec82a729a 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -9,42 +9,42 @@ import {_updateBezierControlPoints} from '../helpers/helpers.curve'; */ function setStyle(ctx, vm) { - ctx.lineCap = vm.borderCapStyle; - ctx.setLineDash(vm.borderDash); - ctx.lineDashOffset = vm.borderDashOffset; - ctx.lineJoin = vm.borderJoinStyle; - ctx.lineWidth = vm.borderWidth; - ctx.strokeStyle = vm.borderColor; + ctx.lineCap = vm.borderCapStyle; + ctx.setLineDash(vm.borderDash); + ctx.lineDashOffset = vm.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle; + ctx.lineWidth = vm.borderWidth; + ctx.strokeStyle = vm.borderColor; } function lineTo(ctx, previous, target) { - ctx.lineTo(target.x, target.y); + ctx.lineTo(target.x, target.y); } function getLineMethod(options) { - if (options.stepped) { - return _steppedLineTo; - } + if (options.stepped) { + return _steppedLineTo; + } - if (options.tension) { - return _bezierCurveTo; - } + if (options.tension) { + return _bezierCurveTo; + } - return lineTo; + return lineTo; } function pathVars(points, segment, params) { - params = params || {}; - const count = points.length; - const start = Math.max(params.start || 0, segment.start); - const end = Math.min(params.end || count - 1, segment.end); - - return { - count, - start, - loop: segment.loop, - ilen: end < start ? count + end - start : end - start - }; + params = params || {}; + const count = points.length; + const start = Math.max(params.start || 0, segment.start); + const end = Math.min(params.end || count - 1, segment.end); + + return { + count, + start, + loop: segment.loop, + ilen: end < start ? count + end - start : end - start + }; } /** @@ -63,35 +63,35 @@ function pathVars(points, segment, params) { * @param {number} params.end - limit segment to points ending at `start` + `count` index */ function pathSegment(ctx, line, segment, params) { - const {points, options} = line; - const {count, start, loop, ilen} = pathVars(points, segment, params); - const lineMethod = getLineMethod(options); - // eslint-disable-next-line prefer-const - let {move = true, reverse} = params || {}; - let i, point, prev; - - for (i = 0; i <= ilen; ++i) { - point = points[(start + (reverse ? ilen - i : i)) % count]; - - if (point.skip) { - // If there is a skipped point inside a segment, spanGaps must be true - continue; - } else if (move) { - ctx.moveTo(point.x, point.y); - move = false; - } else { - lineMethod(ctx, prev, point, reverse, options.stepped); - } - - prev = point; - } - - if (loop) { - point = points[(start + (reverse ? ilen : 0)) % count]; - lineMethod(ctx, prev, point, reverse, options.stepped); - } - - return !!loop; + const {points, options} = line; + const {count, start, loop, ilen} = pathVars(points, segment, params); + const lineMethod = getLineMethod(options); + // eslint-disable-next-line prefer-const + let {move = true, reverse} = params || {}; + let i, point, prev; + + for (i = 0; i <= ilen; ++i) { + point = points[(start + (reverse ? ilen - i : i)) % count]; + + if (point.skip) { + // If there is a skipped point inside a segment, spanGaps must be true + continue; + } else if (move) { + ctx.moveTo(point.x, point.y); + move = false; + } else { + lineMethod(ctx, prev, point, reverse, options.stepped); + } + + prev = point; + } + + if (loop) { + point = points[(start + (reverse ? ilen : 0)) % count]; + lineMethod(ctx, prev, point, reverse, options.stepped); + } + + return !!loop; } /** @@ -110,65 +110,65 @@ function pathSegment(ctx, line, segment, params) { * @param {number} params.end - limit segment to points ending at `start` + `count` index */ function fastPathSegment(ctx, line, segment, params) { - const points = line.points; - const {count, start, ilen} = pathVars(points, segment, params); - const {move = true, reverse} = params || {}; - let avgX = 0; - let countX = 0; - let i, point, prevX, minY, maxY, lastY; - - const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count; - const drawX = () => { - if (minY !== maxY) { - // Draw line to maxY and minY, using the average x-coordinate - ctx.lineTo(avgX, maxY); - ctx.lineTo(avgX, minY); - // Line to y-value of last point in group. So the line continues - // from correct position. Not using move, to have solid path. - ctx.lineTo(avgX, lastY); - } - }; - - if (move) { - point = points[pointIndex(0)]; - ctx.moveTo(point.x, point.y); - } - - for (i = 0; i <= ilen; ++i) { - point = points[pointIndex(i)]; - - if (point.skip) { - // If there is a skipped point inside a segment, spanGaps must be true - continue; - } - - const x = point.x; - const y = point.y; - const truncX = x | 0; // truncated x-coordinate - - if (truncX === prevX) { - // Determine `minY` / `maxY` and `avgX` while we stay within same x-position - if (y < minY) { - minY = y; - } else if (y > maxY) { - maxY = y; - } - // For first point in group, countX is `0`, so average will be `x` / 1. - avgX = (countX * avgX + x) / ++countX; - } else { - drawX(); - // Draw line to next x-position, using the first (or only) - // y-value in that group - ctx.lineTo(x, y); - - prevX = truncX; - countX = 0; - minY = maxY = y; - } - // Keep track of the last y-value in group - lastY = y; - } - drawX(); + const points = line.points; + const {count, start, ilen} = pathVars(points, segment, params); + const {move = true, reverse} = params || {}; + let avgX = 0; + let countX = 0; + let i, point, prevX, minY, maxY, lastY; + + const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count; + const drawX = () => { + if (minY !== maxY) { + // Draw line to maxY and minY, using the average x-coordinate + ctx.lineTo(avgX, maxY); + ctx.lineTo(avgX, minY); + // Line to y-value of last point in group. So the line continues + // from correct position. Not using move, to have solid path. + ctx.lineTo(avgX, lastY); + } + }; + + if (move) { + point = points[pointIndex(0)]; + ctx.moveTo(point.x, point.y); + } + + for (i = 0; i <= ilen; ++i) { + point = points[pointIndex(i)]; + + if (point.skip) { + // If there is a skipped point inside a segment, spanGaps must be true + continue; + } + + const x = point.x; + const y = point.y; + const truncX = x | 0; // truncated x-coordinate + + if (truncX === prevX) { + // Determine `minY` / `maxY` and `avgX` while we stay within same x-position + if (y < minY) { + minY = y; + } else if (y > maxY) { + maxY = y; + } + // For first point in group, countX is `0`, so average will be `x` / 1. + avgX = (countX * avgX + x) / ++countX; + } else { + drawX(); + // Draw line to next x-position, using the first (or only) + // y-value in that group + ctx.lineTo(x, y); + + prevX = truncX; + countX = 0; + minY = maxY = y; + } + // Keep track of the last y-value in group + lastY = y; + } + drawX(); } /** @@ -177,131 +177,131 @@ function fastPathSegment(ctx, line, segment, params) { * @private */ function _getSegmentMethod(line) { - const opts = line.options; - const borderDash = opts.borderDash && opts.borderDash.length; - const useFastPath = !line._loop && !opts.tension && !opts.stepped && !borderDash; - return useFastPath ? fastPathSegment : pathSegment; + const opts = line.options; + const borderDash = opts.borderDash && opts.borderDash.length; + const useFastPath = !line._loop && !opts.tension && !opts.stepped && !borderDash; + return useFastPath ? fastPathSegment : pathSegment; } /** * @private */ function _getInterpolationMethod(options) { - if (options.stepped) { - return _steppedInterpolation; - } + if (options.stepped) { + return _steppedInterpolation; + } - if (options.tension) { - return _bezierInterpolation; - } + if (options.tension) { + return _bezierInterpolation; + } - return _pointInLine; + return _pointInLine; } export default class LineElement extends Element { - constructor(cfg) { - super(); - - this.animated = true; - this.options = undefined; - this._loop = undefined; - this._fullLoop = undefined; - this._path = undefined; - this._points = undefined; - this._segments = undefined; - this._pointsUpdated = false; - - if (cfg) { - Object.assign(this, cfg); - } - } - - updateControlPoints(chartArea) { - const me = this; - const options = me.options; - if (options.tension && !options.stepped && !me._pointsUpdated) { - const loop = options.spanGaps ? me._loop : me._fullLoop; - _updateBezierControlPoints(me._points, options, chartArea, loop); - me._pointsUpdated = true; - } - } - - set points(points) { - const me = this; - me._points = points; - delete me._segments; - delete me._path; - me._pointsUpdated = false; - } - - get points() { - return this._points; - } - - get segments() { - return this._segments || (this._segments = _computeSegments(this)); - } - - /** + constructor(cfg) { + super(); + + this.animated = true; + this.options = undefined; + this._loop = undefined; + this._fullLoop = undefined; + this._path = undefined; + this._points = undefined; + this._segments = undefined; + this._pointsUpdated = false; + + if (cfg) { + Object.assign(this, cfg); + } + } + + updateControlPoints(chartArea) { + const me = this; + const options = me.options; + if (options.tension && !options.stepped && !me._pointsUpdated) { + const loop = options.spanGaps ? me._loop : me._fullLoop; + _updateBezierControlPoints(me._points, options, chartArea, loop); + me._pointsUpdated = true; + } + } + + set points(points) { + const me = this; + me._points = points; + delete me._segments; + delete me._path; + me._pointsUpdated = false; + } + + get points() { + return this._points; + } + + get segments() { + return this._segments || (this._segments = _computeSegments(this)); + } + + /** * First non-skipped point on this line * @returns {PointElement|undefined} */ - first() { - const segments = this.segments; - const points = this.points; - return segments.length && points[segments[0].start]; - } + first() { + const segments = this.segments; + const points = this.points; + return segments.length && points[segments[0].start]; + } - /** + /** * Last non-skipped point on this line * @returns {PointElement|undefined} */ - last() { - const segments = this.segments; - const points = this.points; - const count = segments.length; - return count && points[segments[count - 1].end]; - } - - /** + last() { + const segments = this.segments; + const points = this.points; + const count = segments.length; + return count && points[segments[count - 1].end]; + } + + /** * Interpolate a point in this line at the same value on `property` as * the reference `point` provided * @param {PointElement} point - the reference point * @param {string} property - the property to match on * @returns {PointElement|undefined} */ - interpolate(point, property) { - const me = this; - const options = me.options; - const value = point[property]; - const points = me.points; - const segments = _boundSegments(me, {property, start: value, end: value}); - - if (!segments.length) { - return; - } - - const result = []; - const _interpolate = _getInterpolationMethod(options); - let i, ilen; - for (i = 0, ilen = segments.length; i < ilen; ++i) { - const {start, end} = segments[i]; - const p1 = points[start]; - const p2 = points[end]; - if (p1 === p2) { - result.push(p1); - continue; - } - const t = Math.abs((value - p1[property]) / (p2[property] - p1[property])); - const interpolated = _interpolate(p1, p2, t, options.stepped); - interpolated[property] = point[property]; - result.push(interpolated); - } - return result.length === 1 ? result[0] : result; - } - - /** + interpolate(point, property) { + const me = this; + const options = me.options; + const value = point[property]; + const points = me.points; + const segments = _boundSegments(me, {property, start: value, end: value}); + + if (!segments.length) { + return; + } + + const result = []; + const _interpolate = _getInterpolationMethod(options); + let i, ilen; + for (i = 0, ilen = segments.length; i < ilen; ++i) { + const {start, end} = segments[i]; + const p1 = points[start]; + const p2 = points[end]; + if (p1 === p2) { + result.push(p1); + continue; + } + const t = Math.abs((value - p1[property]) / (p2[property] - p1[property])); + const interpolated = _interpolate(p1, p2, t, options.stepped); + interpolated[property] = point[property]; + result.push(interpolated); + } + return result.length === 1 ? result[0] : result; + } + + /** * Append a segment of this line to current path. * @param {CanvasRenderingContext2D} ctx * @param {object} segment @@ -315,72 +315,72 @@ export default class LineElement extends Element { * @param {number} params.end - limit segment to points ending at `start` + `count` index * @returns {undefined|boolean} - true if the segment is a full loop (path should be closed) */ - pathSegment(ctx, segment, params) { - const segmentMethod = _getSegmentMethod(this); - return segmentMethod(ctx, this, segment, params); - } + pathSegment(ctx, segment, params) { + const segmentMethod = _getSegmentMethod(this); + return segmentMethod(ctx, this, segment, params); + } - /** + /** * Append all segments of this line to current path. * @param {CanvasRenderingContext2D|Path2D} ctx * @param {number} [start] * @param {number} [count] * @returns {undefined|boolean} - true if line is a full loop (path should be closed) */ - path(ctx, start, count) { - const me = this; - const segments = me.segments; - const ilen = segments.length; - const segmentMethod = _getSegmentMethod(me); - let loop = me._loop; - - start = start || 0; - count = count || (me.points.length - start); - - for (let i = 0; i < ilen; ++i) { - loop &= segmentMethod(ctx, me, segments[i], {start, end: start + count - 1}); - } - return !!loop; - } - - /** + path(ctx, start, count) { + const me = this; + const segments = me.segments; + const ilen = segments.length; + const segmentMethod = _getSegmentMethod(me); + let loop = me._loop; + + start = start || 0; + count = count || (me.points.length - start); + + for (let i = 0; i < ilen; ++i) { + loop &= segmentMethod(ctx, me, segments[i], {start, end: start + count - 1}); + } + return !!loop; + } + + /** * Draw * @param {CanvasRenderingContext2D} ctx * @param {object} chartArea * @param {number} [start] * @param {number} [count] */ - draw(ctx, chartArea, start, count) { - const me = this; - const options = me.options || {}; - const points = me.points || []; + draw(ctx, chartArea, start, count) { + const me = this; + const options = me.options || {}; + const points = me.points || []; - if (!points.length || !options.borderWidth) { - return; - } + if (!points.length || !options.borderWidth) { + return; + } - ctx.save(); + ctx.save(); - setStyle(ctx, options); + setStyle(ctx, options); - let path = me._path; - if (!path) { - path = me._path = new Path2D(); - if (me.path(path, start, count)) { - path.closePath(); - } - } + let path = me._path; + if (!path) { + path = me._path = new Path2D(); + if (me.path(path, start, count)) { + path.closePath(); + } + } - ctx.stroke(path); + ctx.stroke(path); - ctx.restore(); + ctx.restore(); - if (me.animated) { - // When line is animated, the control points and path are not cached. - me._pointsUpdated = false; - me._path = undefined; - } - } + if (me.animated) { + // When line is animated, the control points and path are not cached. + me._pointsUpdated = false; + me._path = undefined; + } + } } LineElement.id = 'line'; @@ -389,20 +389,20 @@ LineElement.id = 'line'; * @type {any} */ LineElement.defaults = { - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0, - borderJoinStyle: 'miter', - borderWidth: 3, - capBezierPoints: true, - fill: false, - tension: 0 + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 3, + capBezierPoints: true, + fill: false, + tension: 0 }; /** * @type {any} */ LineElement.defaultRoutes = { - backgroundColor: 'backgroundColor', - borderColor: 'borderColor' + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' }; diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 7f7e75bb050..8ee204c29f0 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -3,67 +3,67 @@ import {drawPoint} from '../helpers/helpers.canvas'; export default class PointElement extends Element { - constructor(cfg) { - super(); - - this.options = undefined; - this.skip = undefined; - this.stop = undefined; - - if (cfg) { - Object.assign(this, cfg); - } - } - - inRange(mouseX, mouseY, useFinalPosition) { - const options = this.options; - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2)); - } - - inXRange(mouseX, useFinalPosition) { - const options = this.options; - const {x} = this.getProps(['x'], useFinalPosition); - - return (Math.abs(mouseX - x) < options.radius + options.hitRadius); - } - - inYRange(mouseY, useFinalPosition) { - const options = this.options; - const {y} = this.getProps(['x'], useFinalPosition); - return (Math.abs(mouseY - y) < options.radius + options.hitRadius); - } - - getCenterPoint(useFinalPosition) { - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return {x, y}; - } - - size() { - const options = this.options || {}; - const radius = Math.max(options.radius, options.hoverRadius) || 0; - const borderWidth = radius && options.borderWidth || 0; - return (radius + borderWidth) * 2; - } - - draw(ctx) { - const me = this; - const options = me.options; - - if (me.skip || options.radius < 0.1) { - return; - } - - ctx.strokeStyle = options.borderColor; - ctx.lineWidth = options.borderWidth; - ctx.fillStyle = options.backgroundColor; - drawPoint(ctx, options, me.x, me.y); - } - - getRange() { - const options = this.options || {}; - return options.radius + options.hitRadius; - } + constructor(cfg) { + super(); + + this.options = undefined; + this.skip = undefined; + this.stop = undefined; + + if (cfg) { + Object.assign(this, cfg); + } + } + + inRange(mouseX, mouseY, useFinalPosition) { + const options = this.options; + const {x, y} = this.getProps(['x', 'y'], useFinalPosition); + return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2)); + } + + inXRange(mouseX, useFinalPosition) { + const options = this.options; + const {x} = this.getProps(['x'], useFinalPosition); + + return (Math.abs(mouseX - x) < options.radius + options.hitRadius); + } + + inYRange(mouseY, useFinalPosition) { + const options = this.options; + const {y} = this.getProps(['x'], useFinalPosition); + return (Math.abs(mouseY - y) < options.radius + options.hitRadius); + } + + getCenterPoint(useFinalPosition) { + const {x, y} = this.getProps(['x', 'y'], useFinalPosition); + return {x, y}; + } + + size() { + const options = this.options || {}; + const radius = Math.max(options.radius, options.hoverRadius) || 0; + const borderWidth = radius && options.borderWidth || 0; + return (radius + borderWidth) * 2; + } + + draw(ctx) { + const me = this; + const options = me.options; + + if (me.skip || options.radius < 0.1) { + return; + } + + ctx.strokeStyle = options.borderColor; + ctx.lineWidth = options.borderWidth; + ctx.fillStyle = options.backgroundColor; + drawPoint(ctx, options, me.x, me.y); + } + + getRange() { + const options = this.options || {}; + return options.radius + options.hitRadius; + } } PointElement.id = 'point'; @@ -72,18 +72,18 @@ PointElement.id = 'point'; * @type {any} */ PointElement.defaults = { - borderWidth: 1, - hitRadius: 1, - hoverBorderWidth: 1, - hoverRadius: 4, - pointStyle: 'circle', - radius: 3 + borderWidth: 1, + hitRadius: 1, + hoverBorderWidth: 1, + hoverRadius: 4, + pointStyle: 'circle', + radius: 3 }; /** * @type {any} */ PointElement.defaultRoutes = { - backgroundColor: 'backgroundColor', - borderColor: 'borderColor' + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' }; diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 25fcfc4bf82..02adec42c25 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -16,11 +16,11 @@ import {PI, TAU, HALF_PI, QUARTER_PI, TWO_THIRDS_PI, RAD_PER_DEG} from './helper * @private */ export function toFontString(font) { - if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) { - return null; - } + if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) { + return null; + } - return (font.style ? font.style + ' ' : '') + return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family; @@ -30,66 +30,66 @@ export function toFontString(font) { * @private */ export function _measureText(ctx, data, gc, longest, string) { - let textWidth = data[string]; - if (!textWidth) { - textWidth = data[string] = ctx.measureText(string).width; - gc.push(string); - } - if (textWidth > longest) { - longest = textWidth; - } - return longest; + let textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; } /** * @private */ export function _longestText(ctx, font, arrayOfThings, cache) { - cache = cache || {}; - let data = cache.data = cache.data || {}; - let gc = cache.garbageCollect = cache.garbageCollect || []; - - if (cache.font !== font) { - data = cache.data = {}; - gc = cache.garbageCollect = []; - cache.font = font; - } - - ctx.save(); - - ctx.font = font; - let longest = 0; - const ilen = arrayOfThings.length; - let i, j, jlen, thing, nestedThing; - for (i = 0; i < ilen; i++) { - thing = arrayOfThings[i]; - - // Undefined strings and arrays should not be measured - if (thing !== undefined && thing !== null && isArray(thing) !== true) { - longest = _measureText(ctx, data, gc, longest, thing); - } else if (isArray(thing)) { - // if it is an array lets measure each element - // to do maybe simplify this function a bit so we can do this more recursively? - for (j = 0, jlen = thing.length; j < jlen; j++) { - nestedThing = thing[j]; - // Undefined strings and arrays should not be measured - if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) { - longest = _measureText(ctx, data, gc, longest, nestedThing); - } - } - } - } - - ctx.restore(); - - const gcLen = gc.length / 2; - if (gcLen > arrayOfThings.length) { - for (i = 0; i < gcLen; i++) { - delete data[gc[i]]; - } - gc.splice(0, gcLen); - } - return longest; + cache = cache || {}; + let data = cache.data = cache.data || {}; + let gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.save(); + + ctx.font = font; + let longest = 0; + const ilen = arrayOfThings.length; + let i, j, jlen, thing, nestedThing; + for (i = 0; i < ilen; i++) { + thing = arrayOfThings[i]; + + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && isArray(thing) !== true) { + longest = _measureText(ctx, data, gc, longest, thing); + } else if (isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + for (j = 0, jlen = thing.length; j < jlen; j++) { + nestedThing = thing[j]; + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) { + longest = _measureText(ctx, data, gc, longest, nestedThing); + } + } + } + } + + ctx.restore(); + + const gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; } /** @@ -101,9 +101,9 @@ export function _longestText(ctx, font, arrayOfThings, cache) { * @private */ export function _alignPixel(chart, pixel, width) { - const devicePixelRatio = chart.currentDevicePixelRatio; - const halfWidth = width / 2; - return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; + const devicePixelRatio = chart.currentDevicePixelRatio; + const halfWidth = width / 2; + return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; } /** @@ -112,132 +112,132 @@ export function _alignPixel(chart, pixel, width) { * @param {CanvasRenderingContext2D} [ctx] */ export function clearCanvas(canvas, ctx) { - ctx = ctx || canvas.getContext('2d'); - - ctx.save(); - // canvas.width and canvas.height do not consider the canvas transform, - // while clearRect does - ctx.resetTransform(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.restore(); + ctx = ctx || canvas.getContext('2d'); + + ctx.save(); + // canvas.width and canvas.height do not consider the canvas transform, + // while clearRect does + ctx.resetTransform(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.restore(); } export function drawPoint(ctx, options, x, y) { - let type, xOffset, yOffset, size, cornerRadius; - const style = options.pointStyle; - const rotation = options.rotation; - const radius = options.radius; - let rad = (rotation || 0) * RAD_PER_DEG; - - if (style && typeof style === 'object') { - type = style.toString(); - if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { - ctx.save(); - ctx.translate(x, y); - ctx.rotate(rad); - ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); - ctx.restore(); - return; - } - } - - if (isNaN(radius) || radius <= 0) { - return; - } - - ctx.beginPath(); - - switch (style) { - // Default includes circle - default: - ctx.arc(x, y, radius, 0, TAU); - ctx.closePath(); - break; - case 'triangle': - 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 'rectRounded': - // 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': - 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 'crossRot': - 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': - 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': - 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(x, y); - ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); - break; - } - - ctx.fill(); - if (options.borderWidth > 0) { - ctx.stroke(); - } + let type, xOffset, yOffset, size, cornerRadius; + const style = options.pointStyle; + const rotation = options.rotation; + const radius = options.radius; + let rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, TAU); + ctx.closePath(); + break; + case 'triangle': + 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 'rectRounded': + // 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': + 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 'crossRot': + 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': + 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': + 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(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); + if (options.borderWidth > 0) { + ctx.stroke(); + } } /** @@ -248,131 +248,131 @@ export function drawPoint(ctx, options, x, y) { * @private */ export function _isPointInArea(point, area) { - const epsilon = 0.5; // margin - to match rounded decimals + const epsilon = 0.5; // margin - to match rounded decimals - return point.x > area.left - epsilon && point.x < area.right + epsilon && + return point.x > area.left - epsilon && point.x < area.right + epsilon && point.y > area.top - epsilon && point.y < area.bottom + epsilon; } export function clipArea(ctx, area) { - ctx.save(); - ctx.beginPath(); - ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); - ctx.clip(); + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); } export function unclipArea(ctx) { - ctx.restore(); + ctx.restore(); } /** * @private */ export function _steppedLineTo(ctx, previous, target, flip, mode) { - if (!previous) { - return ctx.lineTo(target.x, target.y); - } - if (mode === 'middle') { - const midpoint = (previous.x + target.x) / 2.0; - ctx.lineTo(midpoint, previous.y); - ctx.lineTo(midpoint, target.y); - } else if (mode === 'after' !== !!flip) { - ctx.lineTo(previous.x, target.y); - } else { - ctx.lineTo(target.x, previous.y); - } - ctx.lineTo(target.x, target.y); + if (!previous) { + return ctx.lineTo(target.x, target.y); + } + if (mode === 'middle') { + const midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, previous.y); + ctx.lineTo(midpoint, target.y); + } else if (mode === 'after' !== !!flip) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); } /** * @private */ export function _bezierCurveTo(ctx, previous, target, flip) { - if (!previous) { - return ctx.lineTo(target.x, target.y); - } - ctx.bezierCurveTo( - flip ? previous.controlPointPreviousX : previous.controlPointNextX, - flip ? previous.controlPointPreviousY : previous.controlPointNextY, - flip ? target.controlPointNextX : target.controlPointPreviousX, - flip ? target.controlPointNextY : target.controlPointPreviousY, - target.x, - target.y); + if (!previous) { + return ctx.lineTo(target.x, target.y); + } + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); } /** * Render text onto the canvas */ export function renderText(ctx, text, x, y, font, opts = {}) { - const lines = isArray(text) ? text : [text]; - const stroke = opts.strokeWidth > 0 && opts.strokeColor !== ''; - let i, line; + const lines = isArray(text) ? text : [text]; + const stroke = opts.strokeWidth > 0 && opts.strokeColor !== ''; + let i, line; - ctx.save(); + ctx.save(); - if (opts.translation) { - ctx.translate(opts.translation[0], opts.translation[1]); - } + if (opts.translation) { + ctx.translate(opts.translation[0], opts.translation[1]); + } - if (!isNullOrUndef(opts.rotation)) { - ctx.rotate(opts.rotation); - } + if (!isNullOrUndef(opts.rotation)) { + ctx.rotate(opts.rotation); + } - ctx.font = font.string; + ctx.font = font.string; - if (opts.color) { - ctx.fillStyle = opts.color; - } + if (opts.color) { + ctx.fillStyle = opts.color; + } - if (opts.textAlign) { - ctx.textAlign = opts.textAlign; - } + if (opts.textAlign) { + ctx.textAlign = opts.textAlign; + } - if (opts.textBaseline) { - ctx.textBaseline = opts.textBaseline; - } + if (opts.textBaseline) { + ctx.textBaseline = opts.textBaseline; + } - for (i = 0; i < lines.length; ++i) { - line = lines[i]; + for (i = 0; i < lines.length; ++i) { + line = lines[i]; - if (stroke) { - if (opts.strokeColor) { - ctx.strokeStyle = opts.strokeColor; - } + if (stroke) { + if (opts.strokeColor) { + ctx.strokeStyle = opts.strokeColor; + } - if (!isNullOrUndef(opts.strokeWidth)) { - ctx.lineWidth = opts.strokeWidth; - } + if (!isNullOrUndef(opts.strokeWidth)) { + ctx.lineWidth = opts.strokeWidth; + } - ctx.strokeText(line, x, y, opts.maxWidth); - } + ctx.strokeText(line, x, y, opts.maxWidth); + } - ctx.fillText(line, x, y, opts.maxWidth); + ctx.fillText(line, x, y, opts.maxWidth); - if (opts.strikethrough || opts.underline) { - /** + if (opts.strikethrough || opts.underline) { + /** * Now that IE11 support has been dropped, we can use more * of the TextMetrics object. The actual bounding boxes * are unflagged in Chrome, Firefox, Edge, and Safari so they * can be safely used. * See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility */ - const metrics = ctx.measureText(line); - const left = x - metrics.actualBoundingBoxLeft; - const right = x + metrics.actualBoundingBoxRight; - const top = y - metrics.actualBoundingBoxAscent; - const bottom = y + metrics.actualBoundingBoxDescent; - const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom; - - ctx.strokeStyle = ctx.fillStyle; - ctx.beginPath(); - ctx.lineWidth = opts.decorationWidth || 2; - ctx.moveTo(left, yDecoration); - ctx.lineTo(right, yDecoration); - ctx.stroke(); - } - y += font.lineHeight; - } - - ctx.restore(); + const metrics = ctx.measureText(line); + const left = x - metrics.actualBoundingBoxLeft; + const right = x + metrics.actualBoundingBoxRight; + const top = y - metrics.actualBoundingBoxAscent; + const bottom = y + metrics.actualBoundingBoxDescent; + const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom; + + ctx.strokeStyle = ctx.fillStyle; + ctx.beginPath(); + ctx.lineWidth = opts.decorationWidth || 2; + ctx.moveTo(left, yDecoration); + ctx.lineTo(right, yDecoration); + ctx.stroke(); + } + y += font.lineHeight; + } + + ctx.restore(); } diff --git a/src/helpers/helpers.collection.js b/src/helpers/helpers.collection.js index d973464d2dd..66f2a557b07 100644 --- a/src/helpers/helpers.collection.js +++ b/src/helpers/helpers.collection.js @@ -8,21 +8,21 @@ import {_capitalize} from './helpers.core'; * @private */ export function _lookup(table, value, cmp) { - cmp = cmp || ((index) => table[index] < value); - let hi = table.length - 1; - let lo = 0; - let mid; - - while (hi - lo > 1) { - mid = (lo + hi) >> 1; - if (cmp(mid)) { - lo = mid; - } else { - hi = mid; - } - } - - return {lo, hi}; + cmp = cmp || ((index) => table[index] < value); + let hi = table.length - 1; + let lo = 0; + let mid; + + while (hi - lo > 1) { + mid = (lo + hi) >> 1; + if (cmp(mid)) { + lo = mid; + } else { + hi = mid; + } + } + + return {lo, hi}; } /** @@ -33,7 +33,7 @@ export function _lookup(table, value, cmp) { * @private */ export const _lookupByKey = (table, key, value) => - _lookup(table, value, index => table[index][key] < value); + _lookup(table, value, index => table[index][key] < value); /** * Reverse binary search @@ -43,7 +43,7 @@ export const _lookupByKey = (table, key, value) => * @private */ export const _rlookupByKey = (table, key, value) => - _lookup(table, value, index => table[index][key] >= value); + _lookup(table, value, index => table[index][key] >= value); /** * Return subset of `values` between `min` and `max` inclusive. @@ -53,19 +53,19 @@ export const _rlookupByKey = (table, key, value) => * @param {number} max - max value */ export function _filterBetween(values, min, max) { - let start = 0; - let end = values.length; - - while (start < end && values[start] < min) { - start++; - } - while (end > start && values[end - 1] > max) { - end--; - } - - return start > 0 || end < values.length - ? values.slice(start, end) - : values; + let start = 0; + let end = values.length; + + while (start < end && values[start] < min) { + start++; + } + while (end > start && values[end - 1] > max) { + end--; + } + + return start > 0 || end < values.length + ? values.slice(start, end) + : values; } const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; @@ -76,39 +76,39 @@ const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; * called on the '_onData*' callbacks (e.g. _onDataPush, etc.) with same arguments. */ export function listenArrayEvents(array, listener) { - if (array._chartjs) { - array._chartjs.listeners.push(listener); - return; - } - - Object.defineProperty(array, '_chartjs', { - configurable: true, - enumerable: false, - value: { - listeners: [listener] - } - }); - - arrayEvents.forEach((key) => { - const method = '_onData' + _capitalize(key); - const base = array[key]; - - Object.defineProperty(array, key, { - configurable: true, - enumerable: false, - value(...args) { - const res = base.apply(this, args); - - array._chartjs.listeners.forEach((object) => { - if (typeof object[method] === 'function') { - object[method](...args); - } - }); - - return res; - } - }); - }); + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach((key) => { + const method = '_onData' + _capitalize(key); + const base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value(...args) { + const res = base.apply(this, args); + + array._chartjs.listeners.forEach((object) => { + if (typeof object[method] === 'function') { + object[method](...args); + } + }); + + return res; + } + }); + }); } @@ -117,46 +117,46 @@ export function listenArrayEvents(array, listener) { * the _chartjs stub and overridden methods) if array doesn't have any more listeners. */ export function unlistenArrayEvents(array, listener) { - const stub = array._chartjs; - if (!stub) { - return; - } - - const listeners = stub.listeners; - const index = listeners.indexOf(listener); - if (index !== -1) { - listeners.splice(index, 1); - } - - if (listeners.length > 0) { - return; - } - - arrayEvents.forEach((key) => { - delete array[key]; - }); - - delete array._chartjs; + const stub = array._chartjs; + if (!stub) { + return; + } + + const listeners = stub.listeners; + const index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach((key) => { + delete array[key]; + }); + + delete array._chartjs; } /** * @param {Array} items */ export function _arrayUnique(items) { - const set = new Set(); - let i, ilen; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - set.add(items[i]); - } - - if (set.size === ilen) { - return items; - } - - const result = []; - set.forEach(item => { - result.push(item); - }); - return result; + const set = new Set(); + let i, ilen; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + set.add(items[i]); + } + + if (set.size === ilen) { + return items; + } + + const result = []; + set.forEach(item => { + result.push(item); + }); + return result; } diff --git a/src/helpers/helpers.color.js b/src/helpers/helpers.color.js index 50d4fa4f78c..62e66716153 100644 --- a/src/helpers/helpers.color.js +++ b/src/helpers/helpers.color.js @@ -3,11 +3,11 @@ import colorLib from '@kurkle/color'; const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern; export function color(value) { - return isPatternOrGradient(value) ? value : colorLib(value); + return isPatternOrGradient(value) ? value : colorLib(value); } export function getHoverColor(value) { - return isPatternOrGradient(value) - ? value - : colorLib(value).saturate(0.5).darken(0.1).hexString(); + return isPatternOrGradient(value) + ? value + : colorLib(value).saturate(0.5).darken(0.1).hexString(); } diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 5a4da3489a4..a0c63fde08b 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -12,10 +12,10 @@ export function noop() {} * @function */ export const uid = (function() { - let id = 0; - return function() { - return id++; - }; + let id = 0; + return function() { + return id++; + }; }()); /** @@ -25,7 +25,7 @@ export const uid = (function() { * @since 2.7.0 */ export function isNullOrUndef(value) { - return value === null || typeof value === 'undefined'; + return value === null || typeof value === 'undefined'; } /** @@ -35,14 +35,14 @@ export function isNullOrUndef(value) { * @function */ export function isArray(value) { - if (Array.isArray && Array.isArray(value)) { - return true; - } - const type = Object.prototype.toString.call(value); - if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { - return true; - } - return false; + if (Array.isArray && Array.isArray(value)) { + return true; + } + const type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; } /** @@ -52,7 +52,7 @@ export function isArray(value) { * @since 2.7.0 */ export function isObject(value) { - return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; } /** @@ -62,7 +62,7 @@ export function isObject(value) { */ const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value); export { - isNumberFinite as isFinite, + isNumberFinite as isFinite, }; /** @@ -72,7 +72,7 @@ export { * @returns {*} */ export function finiteOrDefault(value, defaultValue) { - return isNumberFinite(value) ? value : defaultValue; + return isNumberFinite(value) ? value : defaultValue; } /** @@ -82,7 +82,7 @@ export function finiteOrDefault(value, defaultValue) { * @returns {*} */ export function valueOrDefault(value, defaultValue) { - return typeof value === 'undefined' ? defaultValue : value; + return typeof value === 'undefined' ? defaultValue : value; } /** @@ -94,9 +94,9 @@ export function valueOrDefault(value, defaultValue) { * @returns {*} */ export function callback(fn, args, thisArg) { - if (fn && typeof fn.call === 'function') { - return fn.apply(thisArg, args); - } + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } } /** @@ -109,25 +109,25 @@ export function callback(fn, args, thisArg) { * @param {boolean} [reverse] - If true, iterates backward on the loopable. */ export function each(loopable, fn, thisArg, reverse) { - let i, len, keys; - if (isArray(loopable)) { - len = loopable.length; - if (reverse) { - for (i = len - 1; i >= 0; i--) { - fn.call(thisArg, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[i], i); - } - } - } else if (isObject(loopable)) { - keys = Object.keys(loopable); - len = keys.length; - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[keys[i]], keys[i]); - } - } + let i, len, keys; + if (isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } } /** @@ -138,22 +138,22 @@ export function each(loopable, fn, thisArg, reverse) { * @private */ export function _elementsEqual(a0, a1) { - let i, ilen, v0, v1; + let i, ilen, v0, v1; - if (!a0 || !a1 || a0.length !== a1.length) { - return false; - } + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } - for (i = 0, ilen = a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; - if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) { - return false; - } - } + if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) { + return false; + } + } - return true; + return true; } /** @@ -162,28 +162,28 @@ export function _elementsEqual(a0, a1) { * @returns {*} */ export function clone(source) { - if (isArray(source)) { - return source.map(clone); - } + if (isArray(source)) { + return source.map(clone); + } - if (isObject(source)) { - const target = Object.create(null); - const keys = Object.keys(source); - const klen = keys.length; - let k = 0; + if (isObject(source)) { + const target = Object.create(null); + const keys = Object.keys(source); + const klen = keys.length; + let k = 0; - for (; k < klen; ++k) { - target[keys[k]] = clone(source[keys[k]]); - } + for (; k < klen; ++k) { + target[keys[k]] = clone(source[keys[k]]); + } - return target; - } + return target; + } - return source; + return source; } function isValidKey(key) { - return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1; + return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1; } /** @@ -192,19 +192,19 @@ function isValidKey(key) { * @private */ export function _merger(key, target, source, options) { - if (!isValidKey(key)) { - return; - } - - const tval = target[key]; - const sval = source[key]; - - if (isObject(tval) && isObject(sval)) { - // eslint-disable-next-line no-use-before-define - merge(tval, sval, options); - } else { - target[key] = clone(sval); - } + if (!isValidKey(key)) { + return; + } + + const tval = target[key]; + const sval = source[key]; + + if (isObject(tval) && isObject(sval)) { + // eslint-disable-next-line no-use-before-define + merge(tval, sval, options); + } else { + target[key] = clone(sval); + } } /** @@ -217,29 +217,29 @@ export function _merger(key, target, source, options) { * @returns {object} The `target` object. */ export function merge(target, source, options) { - const sources = isArray(source) ? source : [source]; - const ilen = sources.length; + const sources = isArray(source) ? source : [source]; + const ilen = sources.length; - if (!isObject(target)) { - return target; - } + if (!isObject(target)) { + return target; + } - options = options || {}; - const merger = options.merger || _merger; + options = options || {}; + const merger = options.merger || _merger; - for (let i = 0; i < ilen; ++i) { - source = sources[i]; - if (!isObject(source)) { - continue; - } + for (let i = 0; i < ilen; ++i) { + source = sources[i]; + if (!isObject(source)) { + continue; + } - const keys = Object.keys(source); - for (let k = 0, klen = keys.length; k < klen; ++k) { - merger(keys[k], target, source, options); - } - } + const keys = Object.keys(source); + for (let k = 0, klen = keys.length; k < klen; ++k) { + merger(keys[k], target, source, options); + } + } - return target; + return target; } /** @@ -250,8 +250,8 @@ export function merge(target, source, options) { * @returns {object} The `target` object. */ export function mergeIf(target, source) { - // eslint-disable-next-line no-use-before-define - return merge(target, source, {merger: _mergerIf}); + // eslint-disable-next-line no-use-before-define + return merge(target, source, {merger: _mergerIf}); } /** @@ -259,53 +259,53 @@ export function mergeIf(target, source) { * @private */ export function _mergerIf(key, target, source) { - if (!isValidKey(key)) { - return; - } - - const tval = target[key]; - const sval = source[key]; - - if (isObject(tval) && isObject(sval)) { - mergeIf(tval, sval); - } else if (!Object.prototype.hasOwnProperty.call(target, key)) { - target[key] = clone(sval); - } + if (!isValidKey(key)) { + return; + } + + const tval = target[key]; + const sval = source[key]; + + if (isObject(tval) && isObject(sval)) { + mergeIf(tval, sval); + } else if (!Object.prototype.hasOwnProperty.call(target, key)) { + target[key] = clone(sval); + } } /** * @private */ export function _deprecated(scope, value, previous, current) { - if (value !== undefined) { - console.warn(scope + ': "' + previous + + if (value !== undefined) { + console.warn(scope + ': "' + previous + '" is deprecated. Please use "' + current + '" instead'); - } + } } export function resolveObjectKey(obj, key) { - // Special cases for `x` and `y` keys. It's quite a lot faster to aceess this way. - // Those are the default keys Chart.js is resolving, so it makes sense to be fast. - if (key === 'x') { - return obj.x; - } - if (key === 'y') { - return obj.y; - } - const keys = key.split('.'); - for (let i = 0, n = keys.length; i < n && obj; ++i) { - const k = keys[i]; - if (!k) { - break; - } - obj = obj[k]; - } - return obj; + // Special cases for `x` and `y` keys. It's quite a lot faster to aceess this way. + // Those are the default keys Chart.js is resolving, so it makes sense to be fast. + if (key === 'x') { + return obj.x; + } + if (key === 'y') { + return obj.y; + } + const keys = key.split('.'); + for (let i = 0, n = keys.length; i < n && obj; ++i) { + const k = keys[i]; + if (!k) { + break; + } + obj = obj[k]; + } + return obj; } /** * @private */ export function _capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); + return str.charAt(0).toUpperCase() + str.slice(1); } diff --git a/src/helpers/helpers.curve.js b/src/helpers/helpers.curve.js index b4a4fe9ab38..4a2445cf60e 100644 --- a/src/helpers/helpers.curve.js +++ b/src/helpers/helpers.curve.js @@ -4,184 +4,184 @@ import {_isPointInArea} from './helpers.canvas'; const EPSILON = Number.EPSILON || 1e-14; export function splineCurve(firstPoint, middlePoint, afterPoint, t) { - // Props to Rob Spencer at scaled innovation for his post on splining between points - // http://scaledinnovation.com/analytics/splines/aboutSplines.html - - // This function must also respect "skipped" points - - const previous = firstPoint.skip ? middlePoint : firstPoint; - const current = middlePoint; - const next = afterPoint.skip ? middlePoint : afterPoint; - - const d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); - const d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); - - let s01 = d01 / (d01 + d12); - let s12 = d12 / (d01 + d12); - - // If all points are the same, s01 & s02 will be inf - s01 = isNaN(s01) ? 0 : s01; - s12 = isNaN(s12) ? 0 : s12; - - const fa = t * s01; // scaling factor for triangle Ta - const fb = t * s12; - - return { - previous: { - x: current.x - fa * (next.x - previous.x), - y: current.y - fa * (next.y - previous.y) - }, - next: { - x: current.x + fb * (next.x - previous.x), - y: current.y + fb * (next.y - previous.y) - } - }; + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + const previous = firstPoint.skip ? middlePoint : firstPoint; + const current = middlePoint; + const next = afterPoint.skip ? middlePoint : afterPoint; + + const d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + const d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + let s01 = d01 / (d01 + d12); + let s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + const fa = t * s01; // scaling factor for triangle Ta + const fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; } export function splineCurveMonotone(points) { - // This function calculates Bézier control points in a similar way than |splineCurve|, - // but preserves monotonicity of the provided data and ensures no local extremums are added - // between the dataset discrete points due to the interpolation. - // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - - const pointsWithTangents = (points || []).map((point) => ({ - model: point, - deltaK: 0, - mK: 0 - })); - - // Calculate slopes (deltaK) and initialize tangents (mK) - const pointsLen = pointsWithTangents.length; - let i, pointBefore, pointCurrent, pointAfter; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointAfter && !pointAfter.model.skip) { - const slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); - - // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 - pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; - } - - if (!pointBefore || pointBefore.model.skip) { - pointCurrent.mK = pointCurrent.deltaK; - } else if (!pointAfter || pointAfter.model.skip) { - pointCurrent.mK = pointBefore.deltaK; - } else if (sign(pointBefore.deltaK) !== sign(pointCurrent.deltaK)) { - pointCurrent.mK = 0; - } else { - pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; - } - } - - // Adjust tangents to ensure monotonic properties - let alphaK, betaK, tauK, squaredMagnitude; - for (i = 0; i < pointsLen - 1; ++i) { - pointCurrent = pointsWithTangents[i]; - pointAfter = pointsWithTangents[i + 1]; - if (pointCurrent.model.skip || pointAfter.model.skip) { - continue; - } - - if (almostEquals(pointCurrent.deltaK, 0, EPSILON)) { - pointCurrent.mK = pointAfter.mK = 0; - continue; - } - - alphaK = pointCurrent.mK / pointCurrent.deltaK; - betaK = pointAfter.mK / pointCurrent.deltaK; - squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); - if (squaredMagnitude <= 9) { - continue; - } - - tauK = 3 / Math.sqrt(squaredMagnitude); - pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; - pointAfter.mK = betaK * tauK * pointCurrent.deltaK; - } - - // Compute control points - let deltaX; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointBefore && !pointBefore.model.skip) { - deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; - pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; - pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; - } - if (pointAfter && !pointAfter.model.skip) { - deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; - pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; - pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; - } - } + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + const pointsWithTangents = (points || []).map((point) => ({ + model: point, + deltaK: 0, + mK: 0 + })); + + // Calculate slopes (deltaK) and initialize tangents (mK) + const pointsLen = pointsWithTangents.length; + let i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + const slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (sign(pointBefore.deltaK) !== sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + let alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (almostEquals(pointCurrent.deltaK, 0, EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + let deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } } function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); + return Math.max(Math.min(pt, max), min); } function capBezierPoints(points, area) { - let i, ilen, point; - for (i = 0, ilen = points.length; i < ilen; ++i) { - point = points[i]; - if (!_isPointInArea(point, area)) { - continue; - } - if (i > 0 && _isPointInArea(points[i - 1], area)) { - point.controlPointPreviousX = capControlPoint(point.controlPointPreviousX, area.left, area.right); - point.controlPointPreviousY = capControlPoint(point.controlPointPreviousY, area.top, area.bottom); - } - if (i < points.length - 1 && _isPointInArea(points[i + 1], area)) { - point.controlPointNextX = capControlPoint(point.controlPointNextX, area.left, area.right); - point.controlPointNextY = capControlPoint(point.controlPointNextY, area.top, area.bottom); - } - } + let i, ilen, point; + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + if (!_isPointInArea(point, area)) { + continue; + } + if (i > 0 && _isPointInArea(points[i - 1], area)) { + point.controlPointPreviousX = capControlPoint(point.controlPointPreviousX, area.left, area.right); + point.controlPointPreviousY = capControlPoint(point.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && _isPointInArea(points[i + 1], area)) { + point.controlPointNextX = capControlPoint(point.controlPointNextX, area.left, area.right); + point.controlPointNextY = capControlPoint(point.controlPointNextY, area.top, area.bottom); + } + } } /** * @private */ export function _updateBezierControlPoints(points, options, area, loop) { - let i, ilen, point, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (options.spanGaps) { - points = points.filter((pt) => !pt.skip); - } - - if (options.cubicInterpolationMode === 'monotone') { - splineCurveMonotone(points); - } else { - let prev = loop ? points[points.length - 1] : points[0]; - for (i = 0, ilen = points.length; i < ilen; ++i) { - point = points[i]; - controlPoints = splineCurve( - prev, - point, - points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], - options.tension - ); - point.controlPointPreviousX = controlPoints.previous.x; - point.controlPointPreviousY = controlPoints.previous.y; - point.controlPointNextX = controlPoints.next.x; - point.controlPointNextY = controlPoints.next.y; - prev = point; - } - } - - if (options.capBezierPoints) { - capBezierPoints(points, area); - } + let i, ilen, point, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (options.spanGaps) { + points = points.filter((pt) => !pt.skip); + } + + if (options.cubicInterpolationMode === 'monotone') { + splineCurveMonotone(points); + } else { + let prev = loop ? points[points.length - 1] : points[0]; + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + controlPoints = splineCurve( + prev, + point, + points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], + options.tension + ); + point.controlPointPreviousX = controlPoints.previous.x; + point.controlPointPreviousY = controlPoints.previous.y; + point.controlPointNextX = controlPoints.next.x; + point.controlPointNextY = controlPoints.next.y; + prev = point; + } + } + + if (options.capBezierPoints) { + capBezierPoints(points, area); + } } diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index 9eacb106144..ea032113d1b 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -4,11 +4,11 @@ import {INFINITY} from './helpers.math'; * @private */ export function _getParentNode(domNode) { - let parent = domNode.parentNode; - if (parent && parent.toString() === '[object ShadowRoot]') { - parent = parent.host; - } - return parent; + let parent = domNode.parentNode; + if (parent && parent.toString() === '[object ShadowRoot]') { + parent = parent.host; + } + return parent; } /** @@ -16,146 +16,146 @@ export function _getParentNode(domNode) { * @private */ function parseMaxStyle(styleValue, node, parentProperty) { - let valueInPixels; - if (typeof styleValue === 'string') { - valueInPixels = parseInt(styleValue, 10); - - if (styleValue.indexOf('%') !== -1) { - // percentage * size in dimension - valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; - } - } else { - valueInPixels = styleValue; - } - - return valueInPixels; + let valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; } const getComputedStyle = (element) => window.getComputedStyle(element, null); export function getStyle(el, property) { - return getComputedStyle(el).getPropertyValue(property); + return getComputedStyle(el).getPropertyValue(property); } const positions = ['top', 'right', 'bottom', 'left']; function getPositionedStyle(styles, style, suffix) { - const result = {}; - suffix = suffix ? '-' + suffix : ''; - for (let i = 0; i < 4; i++) { - const pos = positions[i]; - result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0; - } - result.width = result.left + result.right; - result.height = result.top + result.bottom; - return result; + const result = {}; + suffix = suffix ? '-' + suffix : ''; + for (let i = 0; i < 4; i++) { + const pos = positions[i]; + result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0; + } + result.width = result.left + result.right; + result.height = result.top + result.bottom; + return result; } const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot); function getCanvasPosition(evt, canvas) { - const e = evt.originalEvent || evt; - const touches = e.touches; - const source = touches && touches.length ? touches[0] : e; - const {offsetX, offsetY} = source; - let box = false; - let x, y; - if (useOffsetPos(offsetX, offsetY, e.target)) { - x = offsetX; - y = offsetY; - } else { - const rect = canvas.getBoundingClientRect(); - x = source.clientX - rect.left; - y = source.clientY - rect.top; - box = true; - } - return {x, y, box}; + const e = evt.originalEvent || evt; + const touches = e.touches; + const source = touches && touches.length ? touches[0] : e; + const {offsetX, offsetY} = source; + let box = false; + let x, y; + if (useOffsetPos(offsetX, offsetY, e.target)) { + x = offsetX; + y = offsetY; + } else { + const rect = canvas.getBoundingClientRect(); + x = source.clientX - rect.left; + y = source.clientY - rect.top; + box = true; + } + return {x, y, box}; } export function getRelativePosition(evt, chart) { - const {canvas, currentDevicePixelRatio} = chart; - const style = getComputedStyle(canvas); - const borderBox = style.boxSizing === 'border-box'; - const paddings = getPositionedStyle(style, 'padding'); - const borders = getPositionedStyle(style, 'border', 'width'); - const {x, y, box} = getCanvasPosition(evt, canvas); - const xOffset = paddings.left + (box && borders.left); - const yOffset = paddings.top + (box && borders.top); - - let {width, height} = chart; - if (borderBox) { - width -= paddings.width + borders.width; - height -= paddings.height + borders.height; - } - return { - x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio), - y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio) - }; + const {canvas, currentDevicePixelRatio} = chart; + const style = getComputedStyle(canvas); + const borderBox = style.boxSizing === 'border-box'; + const paddings = getPositionedStyle(style, 'padding'); + const borders = getPositionedStyle(style, 'border', 'width'); + const {x, y, box} = getCanvasPosition(evt, canvas); + const xOffset = paddings.left + (box && borders.left); + const yOffset = paddings.top + (box && borders.top); + + let {width, height} = chart; + if (borderBox) { + width -= paddings.width + borders.width; + height -= paddings.height + borders.height; + } + return { + x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio), + y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio) + }; } function getContainerSize(canvas, width, height) { - let maxWidth, maxHeight; - - if (width === undefined || height === undefined) { - const container = _getParentNode(canvas); - if (!container) { - width = canvas.clientWidth; - height = canvas.clientHeight; - } else { - const rect = container.getBoundingClientRect(); // this is the border box of the container - const containerStyle = getComputedStyle(container); - const containerBorder = getPositionedStyle(containerStyle, 'border', 'width'); - const containerPadding = getPositionedStyle(containerStyle, 'padding'); - width = rect.width - containerPadding.width - containerBorder.width; - height = rect.height - containerPadding.height - containerBorder.height; - maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth'); - maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight'); - } - } - return { - width, - height, - maxWidth: maxWidth || INFINITY, - maxHeight: maxHeight || INFINITY - }; + let maxWidth, maxHeight; + + if (width === undefined || height === undefined) { + const container = _getParentNode(canvas); + if (!container) { + width = canvas.clientWidth; + height = canvas.clientHeight; + } else { + const rect = container.getBoundingClientRect(); // this is the border box of the container + const containerStyle = getComputedStyle(container); + const containerBorder = getPositionedStyle(containerStyle, 'border', 'width'); + const containerPadding = getPositionedStyle(containerStyle, 'padding'); + width = rect.width - containerPadding.width - containerBorder.width; + height = rect.height - containerPadding.height - containerBorder.height; + maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth'); + maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight'); + } + } + return { + width, + height, + maxWidth: maxWidth || INFINITY, + maxHeight: maxHeight || INFINITY + }; } export function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) { - const style = getComputedStyle(canvas); - const margins = getPositionedStyle(style, 'margin'); - const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY; - const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY; - const containerSize = getContainerSize(canvas, bbWidth, bbHeight); - let {width, height} = containerSize; - - if (style.boxSizing === 'content-box') { - const borders = getPositionedStyle(style, 'border', 'width'); - const paddings = getPositionedStyle(style, 'padding'); - width -= paddings.width + borders.width; - height -= paddings.height + borders.height; - } - width = Math.max(0, width - margins.width); - height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height); - return { - width: Math.min(width, maxWidth, containerSize.maxWidth), - height: Math.min(height, maxHeight, containerSize.maxHeight) - }; + const style = getComputedStyle(canvas); + const margins = getPositionedStyle(style, 'margin'); + const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY; + const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY; + const containerSize = getContainerSize(canvas, bbWidth, bbHeight); + let {width, height} = containerSize; + + if (style.boxSizing === 'content-box') { + const borders = getPositionedStyle(style, 'border', 'width'); + const paddings = getPositionedStyle(style, 'padding'); + width -= paddings.width + borders.width; + height -= paddings.height + borders.height; + } + width = Math.max(0, width - margins.width); + height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height); + return { + width: Math.min(width, maxWidth, containerSize.maxWidth), + height: Math.min(height, maxHeight, containerSize.maxHeight) + }; } export function retinaScale(chart, forceRatio) { - const pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; - const {canvas, width, height} = chart; - - canvas.height = height * pixelRatio; - canvas.width = width * pixelRatio; - chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - - // If no style has been set on the canvas, the render size is used as display size, - // making the chart visually bigger, so let's enforce it to the "correct" values. - // See https://github.com/chartjs/Chart.js/issues/3575 - if (canvas.style && !canvas.style.height && !canvas.style.width) { - canvas.style.height = height + 'px'; - canvas.style.width = width + 'px'; - } + const pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; + const {canvas, width, height} = chart; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + if (canvas.style && !canvas.style.height && !canvas.style.width) { + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + } } /** @@ -164,22 +164,22 @@ export function retinaScale(chart, forceRatio) { * @private */ export const supportsEventListenerOptions = (function() { - let passiveSupported = false; - try { - const options = { - get passive() { // This function will be called when the browser attempts to access the passive property. - passiveSupported = true; - return false; - } - }; - // @ts-ignore - window.addEventListener('test', null, options); - // @ts-ignore - window.removeEventListener('test', null, options); - } catch (e) { - // continue regardless of error - } - return passiveSupported; + let passiveSupported = false; + try { + const options = { + get passive() { // This function will be called when the browser attempts to access the passive property. + passiveSupported = true; + return false; + } + }; + // @ts-ignore + window.addEventListener('test', null, options); + // @ts-ignore + window.removeEventListener('test', null, options); + } catch (e) { + // continue regardless of error + } + return passiveSupported; }()); /** @@ -192,7 +192,7 @@ export const supportsEventListenerOptions = (function() { * @returns {number=} Size in pixels or undefined if unknown. */ export function readUsedSize(element, property) { - const value = getStyle(element, property); - const matches = value && value.match(/^(\d+)(\.\d+)?px$/); - return matches ? +matches[1] : undefined; + const value = getStyle(element, property); + const matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? +matches[1] : undefined; } diff --git a/src/helpers/helpers.easing.js b/src/helpers/helpers.easing.js index f88f222ba34..0fbf717a78c 100644 --- a/src/helpers/helpers.easing.js +++ b/src/helpers/helpers.easing.js @@ -6,230 +6,230 @@ import {PI, TAU, HALF_PI} from './helpers.math'; * @see http://www.robertpenner.com/easing/ */ const effects = { - linear(t) { - return t; - }, - - easeInQuad(t) { - return t * t; - }, - - easeOutQuad(t) { - return -t * (t - 2); - }, - - easeInOutQuad(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t; - } - return -0.5 * ((--t) * (t - 2) - 1); - }, - - easeInCubic(t) { - return t * t * t; - }, - - easeOutCubic(t) { - return (t -= 1) * t * t + 1; - }, - - easeInOutCubic(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t; - } - return 0.5 * ((t -= 2) * t * t + 2); - }, - - easeInQuart(t) { - return t * t * t * t; - }, - - easeOutQuart(t) { - return -((t -= 1) * t * t * t - 1); - }, - - easeInOutQuart(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t; - } - return -0.5 * ((t -= 2) * t * t * t - 2); - }, - - easeInQuint(t) { - return t * t * t * t * t; - }, - - easeOutQuint(t) { - return (t -= 1) * t * t * t * t + 1; - }, - - easeInOutQuint(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t * t; - } - return 0.5 * ((t -= 2) * t * t * t * t + 2); - }, - - easeInSine(t) { - return -Math.cos(t * HALF_PI) + 1; - }, - - easeOutSine(t) { - return Math.sin(t * HALF_PI); - }, - - easeInOutSine(t) { - return -0.5 * (Math.cos(PI * t) - 1); - }, - - easeInExpo(t) { - return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); - }, - - easeOutExpo(t) { - return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; - }, - - easeInOutExpo(t) { - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if ((t /= 0.5) < 1) { - return 0.5 * Math.pow(2, 10 * (t - 1)); - } - return 0.5 * (-Math.pow(2, -10 * --t) + 2); - }, - - easeInCirc(t) { - if (t >= 1) { - return t; - } - return -(Math.sqrt(1 - t * t) - 1); - }, - - easeOutCirc(t) { - return Math.sqrt(1 - (t -= 1) * t); - }, - - easeInOutCirc(t) { - if ((t /= 0.5) < 1) { - return -0.5 * (Math.sqrt(1 - t * t) - 1); - } - return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - - easeInElastic(t) { - let s = 1.70158; - let p = 0; - let a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / TAU * Math.asin(1 / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p)); - }, - - easeOutElastic(t) { - let s = 1.70158; - let p = 0; - let a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / TAU * Math.asin(1 / a); - } - return a * Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1; - }, - - easeInOutElastic(t) { - let s = 1.70158; - let p = 0; - let a = 1; - if (t === 0) { - return 0; - } - if ((t /= 0.5) === 2) { - return 1; - } - if (!p) { - p = 0.45; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / TAU * Math.asin(1 / a); - } - if (t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p)); - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * TAU / p) * 0.5 + 1; - }, - easeInBack(t) { - const s = 1.70158; - return t * t * ((s + 1) * t - s); - }, - - easeOutBack(t) { - const s = 1.70158; - return (t -= 1) * t * ((s + 1) * t + s) + 1; - }, - - easeInOutBack(t) { - let s = 1.70158; - if ((t /= 0.5) < 1) { - return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); - } - return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - - easeInBounce(t) { - return 1 - effects.easeOutBounce(1 - t); - }, - - easeOutBounce(t) { - if (t < (1 / 2.75)) { - return 7.5625 * t * t; - } - if (t < (2 / 2.75)) { - return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; - } - if (t < (2.5 / 2.75)) { - return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; - } - return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; - }, - - easeInOutBounce(t) { - if (t < 0.5) { - return effects.easeInBounce(t * 2) * 0.5; - } - return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; - } + linear(t) { + return t; + }, + + easeInQuad(t) { + return t * t; + }, + + easeOutQuad(t) { + return -t * (t - 2); + }, + + easeInOutQuad(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic(t) { + return t * t * t; + }, + + easeOutCubic(t) { + return (t -= 1) * t * t + 1; + }, + + easeInOutCubic(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart(t) { + return t * t * t * t; + }, + + easeOutQuart(t) { + return -((t -= 1) * t * t * t - 1); + }, + + easeInOutQuart(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint(t) { + return t * t * t * t * t; + }, + + easeOutQuint(t) { + return (t -= 1) * t * t * t * t + 1; + }, + + easeInOutQuint(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine(t) { + return -Math.cos(t * HALF_PI) + 1; + }, + + easeOutSine(t) { + return Math.sin(t * HALF_PI); + }, + + easeInOutSine(t) { + return -0.5 * (Math.cos(PI * t) - 1); + }, + + easeInExpo(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc(t) { + return Math.sqrt(1 - (t -= 1) * t); + }, + + easeInOutCirc(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic(t) { + let s = 1.70158; + let p = 0; + let a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / TAU * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p)); + }, + + easeOutElastic(t) { + let s = 1.70158; + let p = 0; + let a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / TAU * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1; + }, + + easeInOutElastic(t) { + let s = 1.70158; + let p = 0; + let a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / TAU * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * TAU / p) * 0.5 + 1; + }, + easeInBack(t) { + const s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack(t) { + const s = 1.70158; + return (t -= 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack(t) { + let s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } }; export default effects; diff --git a/src/helpers/helpers.extras.js b/src/helpers/helpers.extras.js index 280ad832bc7..21857a6e993 100644 --- a/src/helpers/helpers.extras.js +++ b/src/helpers/helpers.extras.js @@ -1,18 +1,18 @@ export function fontString(pixelSize, fontStyle, fontFamily) { - return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; } /** * Request animation polyfill */ export const requestAnimFrame = (function() { - if (typeof window === 'undefined') { - return function(callback) { - return callback(); - }; - } - return window.requestAnimationFrame; + if (typeof window === 'undefined') { + return function(callback) { + return callback(); + }; + } + return window.requestAnimationFrame; }()); /** @@ -23,21 +23,21 @@ export const requestAnimFrame = (function() { * @param {function} [updateFn] */ export function throttled(fn, thisArg, updateFn) { - const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args)); - let ticking = false; - let args = []; - - return function(...rest) { - args = updateArgs(rest); - - if (!ticking) { - ticking = true; - requestAnimFrame.call(window, () => { - ticking = false; - fn.apply(thisArg, args); - }); - } - }; + const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args)); + let ticking = false; + let args = []; + + return function(...rest) { + args = updateArgs(rest); + + if (!ticking) { + ticking = true; + requestAnimFrame.call(window, () => { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; } diff --git a/src/helpers/helpers.interpolation.js b/src/helpers/helpers.interpolation.js index bf1ac4a8fb2..6441efaad3f 100644 --- a/src/helpers/helpers.interpolation.js +++ b/src/helpers/helpers.interpolation.js @@ -2,34 +2,34 @@ * @private */ export function _pointInLine(p1, p2, t, mode) { // eslint-disable-line no-unused-vars - return { - x: p1.x + t * (p2.x - p1.x), - y: p1.y + t * (p2.y - p1.y) - }; + return { + x: p1.x + t * (p2.x - p1.x), + y: p1.y + t * (p2.y - p1.y) + }; } /** * @private */ export function _steppedInterpolation(p1, p2, t, mode) { - return { - x: p1.x + t * (p2.x - p1.x), - y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y - : mode === 'after' ? t < 1 ? p1.y : p2.y - : t > 0 ? p2.y : p1.y - }; + return { + x: p1.x + t * (p2.x - p1.x), + y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y + : mode === 'after' ? t < 1 ? p1.y : p2.y + : t > 0 ? p2.y : p1.y + }; } /** * @private */ export function _bezierInterpolation(p1, p2, t, mode) { // eslint-disable-line no-unused-vars - const cp1 = {x: p1.controlPointNextX, y: p1.controlPointNextY}; - const cp2 = {x: p2.controlPointPreviousX, y: p2.controlPointPreviousY}; - const a = _pointInLine(p1, cp1, t); - const b = _pointInLine(cp1, cp2, t); - const c = _pointInLine(cp2, p2, t); - const d = _pointInLine(a, b, t); - const e = _pointInLine(b, c, t); - return _pointInLine(d, e, t); + const cp1 = {x: p1.controlPointNextX, y: p1.controlPointNextY}; + const cp2 = {x: p2.controlPointPreviousX, y: p2.controlPointPreviousY}; + const a = _pointInLine(p1, cp1, t); + const b = _pointInLine(cp1, cp2, t); + const c = _pointInLine(cp2, p2, t); + const d = _pointInLine(a, b, t); + const e = _pointInLine(b, c, t); + return _pointInLine(d, e, t); } diff --git a/src/helpers/helpers.math.js b/src/helpers/helpers.math.js index 26e522013ee..41c8a6db254 100644 --- a/src/helpers/helpers.math.js +++ b/src/helpers/helpers.math.js @@ -19,80 +19,80 @@ export const TWO_THIRDS_PI = PI * 2 / 3; * @private */ export function _factorize(value) { - const result = []; - const sqrt = Math.sqrt(value); - let i; - - for (i = 1; i < sqrt; i++) { - if (value % i === 0) { - result.push(i); - result.push(value / i); - } - } - if (sqrt === (sqrt | 0)) { // if value is a square number - result.push(sqrt); - } - - result.sort((a, b) => a - b).pop(); - return result; + const result = []; + const sqrt = Math.sqrt(value); + let i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort((a, b) => a - b).pop(); + return result; } export const log10 = Math.log10 || function(x) { - const exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. - // Check for whole powers of 10, - // which due to floating point rounding error should be corrected. - const powerOf10 = Math.round(exponent); - const isPowerOf10 = x === Math.pow(10, powerOf10); + const exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + const powerOf10 = Math.round(exponent); + const isPowerOf10 = x === Math.pow(10, powerOf10); - return isPowerOf10 ? powerOf10 : exponent; + return isPowerOf10 ? powerOf10 : exponent; }; export function isNumber(n) { - return !isNaN(parseFloat(n)) && isFinite(n); + return !isNaN(parseFloat(n)) && isFinite(n); } export function almostEquals(x, y, epsilon) { - return Math.abs(x - y) < epsilon; + return Math.abs(x - y) < epsilon; } export function almostWhole(x, epsilon) { - const rounded = Math.round(x); - return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); + const rounded = Math.round(x); + return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); } /** * @private */ export function _setMinAndMaxByKey(array, target, property) { - let i, ilen, value; - - for (i = 0, ilen = array.length; i < ilen; i++) { - value = array[i][property]; - if (!isNaN(value)) { - target.min = Math.min(target.min, value); - target.max = Math.max(target.max, value); - } - } + let i, ilen, value; + + for (i = 0, ilen = array.length; i < ilen; i++) { + value = array[i][property]; + if (!isNaN(value)) { + target.min = Math.min(target.min, value); + target.max = Math.max(target.max, value); + } + } } export const sign = Math.sign ? - function(x) { - return Math.sign(x); - } : - function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - }; + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; export function toRadians(degrees) { - return degrees * (PI / 180); + return degrees * (PI / 180); } export function toDegrees(radians) { - return radians * (180 / PI); + return radians * (180 / PI); } /** @@ -103,38 +103,38 @@ export function toDegrees(radians) { * @private */ export function _decimalPlaces(x) { - if (!isFiniteNumber(x)) { - return; - } - let e = 1; - let p = 0; - while (Math.round(x * e) / e !== x) { - e *= 10; - p++; - } - return p; + if (!isFiniteNumber(x)) { + return; + } + let e = 1; + let p = 0; + while (Math.round(x * e) / e !== x) { + e *= 10; + p++; + } + return p; } // Gets the angle from vertical upright to the point about a centre. export function getAngleFromPoint(centrePoint, anglePoint) { - const distanceFromXCenter = anglePoint.x - centrePoint.x; - const distanceFromYCenter = anglePoint.y - centrePoint.y; - const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + const distanceFromXCenter = anglePoint.x - centrePoint.x; + const distanceFromYCenter = anglePoint.y - centrePoint.y; + const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); - if (angle < (-0.5 * PI)) { - angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2] - } + if (angle < (-0.5 * PI)) { + angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } - return { - angle, - distance: radialDistanceFromCenter - }; + return { + angle, + distance: radialDistanceFromCenter + }; } export function distanceBetweenPoints(pt1, pt2) { - return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); } /** @@ -142,7 +142,7 @@ export function distanceBetweenPoints(pt1, pt2) { * @private */ export function _angleDiff(a, b) { - return (a - b + PITAU) % TAU - PI; + return (a - b + PITAU) % TAU - PI; } /** @@ -150,21 +150,21 @@ export function _angleDiff(a, b) { * @private */ export function _normalizeAngle(a) { - return (a % TAU + TAU) % TAU; + return (a % TAU + TAU) % TAU; } /** * @private */ export function _angleBetween(angle, start, end) { - const a = _normalizeAngle(angle); - const s = _normalizeAngle(start); - const e = _normalizeAngle(end); - const angleToStart = _normalizeAngle(s - a); - const angleToEnd = _normalizeAngle(e - a); - const startToAngle = _normalizeAngle(a - s); - const endToAngle = _normalizeAngle(a - e); - return a === s || a === e || (angleToStart > angleToEnd && startToAngle < endToAngle); + const a = _normalizeAngle(angle); + const s = _normalizeAngle(start); + const e = _normalizeAngle(end); + const angleToStart = _normalizeAngle(s - a); + const angleToEnd = _normalizeAngle(e - a); + const startToAngle = _normalizeAngle(a - s); + const endToAngle = _normalizeAngle(a - e); + return a === s || a === e || (angleToStart > angleToEnd && startToAngle < endToAngle); } /** @@ -175,9 +175,9 @@ export function _angleBetween(angle, start, end) { * @private */ export function _limitValue(value, min, max) { - return Math.max(min, Math.min(max, value)); + return Math.max(min, Math.min(max, value)); } export function _int16Range(value) { - return _limitValue(value, -32768, 32767); + return _limitValue(value, -32768, 32767); } diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 2b01d518e98..604c09603ab 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -17,24 +17,24 @@ const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); * @since 2.7.0 */ export function toLineHeight(value, size) { - const matches = ('' + value).match(LINE_HEIGHT); - if (!matches || matches[1] === 'normal') { - return size * 1.2; - } - - value = +matches[2]; - - switch (matches[3]) { - case 'px': - return value; - case '%': - value /= 100; - break; - default: - break; - } - - return size * value; + const matches = ('' + value).match(LINE_HEIGHT); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } + + return size * value; } const numberOrZero = v => +v || 0; @@ -47,23 +47,23 @@ const numberOrZero = v => +v || 0; * @since 3.0.0 */ export function toTRBL(value) { - let t, r, b, l; - - if (isObject(value)) { - t = numberOrZero(value.top); - r = numberOrZero(value.right); - b = numberOrZero(value.bottom); - l = numberOrZero(value.left); - } else { - t = r = b = l = numberOrZero(value); - } - - return { - top: t, - right: r, - bottom: b, - left: l - }; + let t, r, b, l; + + if (isObject(value)) { + t = numberOrZero(value.top); + r = numberOrZero(value.right); + b = numberOrZero(value.bottom); + l = numberOrZero(value.left); + } else { + t = r = b = l = numberOrZero(value); + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; } /** @@ -74,23 +74,23 @@ export function toTRBL(value) { * @since 3.0.0 */ export function toTRBLCorners(value) { - let tl, tr, bl, br; - - if (isObject(value)) { - tl = numberOrZero(value.topLeft); - tr = numberOrZero(value.topRight); - bl = numberOrZero(value.bottomLeft); - br = numberOrZero(value.bottomRight); - } else { - tl = tr = bl = br = numberOrZero(value); - } - - return { - topLeft: tl, - topRight: tr, - bottomLeft: bl, - bottomRight: br - }; + let tl, tr, bl, br; + + if (isObject(value)) { + tl = numberOrZero(value.topLeft); + tr = numberOrZero(value.topRight); + bl = numberOrZero(value.bottomLeft); + br = numberOrZero(value.bottomRight); + } else { + tl = tr = bl = br = numberOrZero(value); + } + + return { + topLeft: tl, + topRight: tr, + bottomLeft: bl, + bottomRight: br + }; } /** @@ -101,12 +101,12 @@ export function toTRBLCorners(value) { * @since 2.7.0 */ export function toPadding(value) { - const obj = toTRBL(value); + const obj = toTRBL(value); - obj.width = obj.left + obj.right; - obj.height = obj.top + obj.bottom; + obj.width = obj.left + obj.right; + obj.height = obj.top + obj.bottom; - return obj; + return obj; } /** @@ -117,26 +117,26 @@ export function toPadding(value) { * @private */ export function toFont(options, fallback) { - options = options || {}; - fallback = fallback || defaults.font; - - let size = valueOrDefault(options.size, fallback.size); - - if (typeof size === 'string') { - size = parseInt(size, 10); - } - - const font = { - family: valueOrDefault(options.family, fallback.family), - lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size), - size, - style: valueOrDefault(options.style, fallback.style), - weight: valueOrDefault(options.weight, fallback.weight), - string: '' - }; - - font.string = toFontString(font); - return font; + options = options || {}; + fallback = fallback || defaults.font; + + let size = valueOrDefault(options.size, fallback.size); + + if (typeof size === 'string') { + size = parseInt(size, 10); + } + + const font = { + family: valueOrDefault(options.family, fallback.family), + lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size), + size, + style: valueOrDefault(options.style, fallback.style), + weight: valueOrDefault(options.weight, fallback.weight), + string: '' + }; + + font.string = toFontString(font); + return font; } /** @@ -151,27 +151,27 @@ export function toFont(options, fallback) { * @since 2.7.0 */ export function resolve(inputs, context, index, info) { - let cacheable = true; - let i, ilen, value; - - for (i = 0, ilen = inputs.length; i < ilen; ++i) { - value = inputs[i]; - if (value === undefined) { - continue; - } - if (context !== undefined && typeof value === 'function') { - value = value(context); - cacheable = false; - } - if (index !== undefined && isArray(value)) { - value = value[index % value.length]; - cacheable = false; - } - if (value !== undefined) { - if (info && !cacheable) { - info.cacheable = false; - } - return value; - } - } + let cacheable = true; + let i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && isArray(value)) { + value = value[index % value.length]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } } diff --git a/src/helpers/helpers.rtl.js b/src/helpers/helpers.rtl.js index 5b0d2cda61e..eb11224f8cb 100644 --- a/src/helpers/helpers.rtl.js +++ b/src/helpers/helpers.rtl.js @@ -1,66 +1,66 @@ const getRightToLeftAdapter = function(rectX, width) { - return { - x(x) { - return rectX + rectX + width - x; - }, - setWidth(w) { - width = w; - }, - textAlign(align) { - if (align === 'center') { - return align; - } - return align === 'right' ? 'left' : 'right'; - }, - xPlus(x, value) { - return x - value; - }, - leftForLtr(x, itemWidth) { - return x - itemWidth; - }, - }; + return { + x(x) { + return rectX + rectX + width - x; + }, + setWidth(w) { + width = w; + }, + textAlign(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus(x, value) { + return x - value; + }, + leftForLtr(x, itemWidth) { + return x - itemWidth; + }, + }; }; const getLeftToRightAdapter = function() { - return { - x(x) { - return x; - }, - setWidth(w) { // eslint-disable-line no-unused-vars - }, - textAlign(align) { - return align; - }, - xPlus(x, value) { - return x + value; - }, - leftForLtr(x, _itemWidth) { // eslint-disable-line no-unused-vars - return x; - }, - }; + return { + x(x) { + return x; + }, + setWidth(w) { // eslint-disable-line no-unused-vars + }, + textAlign(align) { + return align; + }, + xPlus(x, value) { + return x + value; + }, + leftForLtr(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; }; export function getRtlAdapter(rtl, rectX, width) { - return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter(); + return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter(); } export function overrideTextDirection(ctx, direction) { - let style, original; - if (direction === 'ltr' || direction === 'rtl') { - style = ctx.canvas.style; - original = [ - style.getPropertyValue('direction'), - style.getPropertyPriority('direction'), - ]; + let style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; - style.setProperty('direction', direction, 'important'); - ctx.prevTextDirection = original; - } + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } } export function restoreTextDirection(ctx, original) { - if (original !== undefined) { - delete ctx.prevTextDirection; - ctx.canvas.style.setProperty('direction', original[0], original[1]); - } + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } } diff --git a/src/helpers/helpers.segment.js b/src/helpers/helpers.segment.js index 632c1e9d739..9a3ce5ec84b 100644 --- a/src/helpers/helpers.segment.js +++ b/src/helpers/helpers.segment.js @@ -6,54 +6,54 @@ import {_angleBetween, _angleDiff, _normalizeAngle} from './helpers.math'; */ function propertyFn(property) { - if (property === 'angle') { - return { - between: _angleBetween, - compare: _angleDiff, - normalize: _normalizeAngle, - }; - } - return { - between: (n, s, e) => n >= s && n <= e, - compare: (a, b) => a - b, - normalize: x => x - }; + if (property === 'angle') { + return { + between: _angleBetween, + compare: _angleDiff, + normalize: _normalizeAngle, + }; + } + return { + between: (n, s, e) => n >= s && n <= e, + compare: (a, b) => a - b, + normalize: x => x + }; } function makeSubSegment(start, end, loop, count) { - return { - start: start % count, - end: end % count, - loop: loop && (end - start + 1) % count === 0 - }; + return { + start: start % count, + end: end % count, + loop: loop && (end - start + 1) % count === 0 + }; } function getSegment(segment, points, bounds) { - const {property, start: startBound, end: endBound} = bounds; - const {between, normalize} = propertyFn(property); - const count = points.length; - // eslint-disable-next-line prefer-const - let {start, end, loop} = segment; - let i, ilen; - - if (loop) { - start += count; - end += count; - for (i = 0, ilen = count; i < ilen; ++i) { - if (!between(normalize(points[start % count][property]), startBound, endBound)) { - break; - } - start--; - end--; - } - start %= count; - end %= count; - } - - if (end < start) { - end += count; - } - return {start, end, loop}; + const {property, start: startBound, end: endBound} = bounds; + const {between, normalize} = propertyFn(property); + const count = points.length; + // eslint-disable-next-line prefer-const + let {start, end, loop} = segment; + let i, ilen; + + if (loop) { + start += count; + end += count; + for (i = 0, ilen = count; i < ilen; ++i) { + if (!between(normalize(points[start % count][property]), startBound, endBound)) { + break; + } + start--; + end--; + } + start %= count; + end %= count; + } + + if (end < start) { + end += count; + } + return {start, end, loop}; } /** @@ -70,52 +70,52 @@ function getSegment(segment, points, bounds) { * @private **/ export function _boundSegment(segment, points, bounds) { - if (!bounds) { - return [segment]; - } - - const {property, start: startBound, end: endBound} = bounds; - const count = points.length; - const {compare, between, normalize} = propertyFn(property); - const {start, end, loop} = getSegment(segment, points, bounds); - - const result = []; - let inside = false; - let subStart = null; - let value, point, prevValue; - - const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0; - const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value); - const shouldStart = () => inside || startIsBefore(); - const shouldStop = () => !inside || endIsBefore(); - - for (let i = start, prev = start; i <= end; ++i) { - point = points[i % count]; - - if (point.skip) { - continue; - } - - value = normalize(point[property]); - inside = between(value, startBound, endBound); - - if (subStart === null && shouldStart()) { - subStart = compare(value, startBound) === 0 ? i : prev; - } - - if (subStart !== null && shouldStop()) { - result.push(makeSubSegment(subStart, i, loop, count)); - subStart = null; - } - prev = i; - prevValue = value; - } - - if (subStart !== null) { - result.push(makeSubSegment(subStart, end, loop, count)); - } - - return result; + if (!bounds) { + return [segment]; + } + + const {property, start: startBound, end: endBound} = bounds; + const count = points.length; + const {compare, between, normalize} = propertyFn(property); + const {start, end, loop} = getSegment(segment, points, bounds); + + const result = []; + let inside = false; + let subStart = null; + let value, point, prevValue; + + const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0; + const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value); + const shouldStart = () => inside || startIsBefore(); + const shouldStop = () => !inside || endIsBefore(); + + for (let i = start, prev = start; i <= end; ++i) { + point = points[i % count]; + + if (point.skip) { + continue; + } + + value = normalize(point[property]); + inside = between(value, startBound, endBound); + + if (subStart === null && shouldStart()) { + subStart = compare(value, startBound) === 0 ? i : prev; + } + + if (subStart !== null && shouldStop()) { + result.push(makeSubSegment(subStart, i, loop, count)); + subStart = null; + } + prev = i; + prevValue = value; + } + + if (subStart !== null) { + result.push(makeSubSegment(subStart, end, loop, count)); + } + + return result; } @@ -129,53 +129,53 @@ export function _boundSegment(segment, points, bounds) { * @private */ export function _boundSegments(line, bounds) { - const result = []; - const segments = line.segments; - - for (let i = 0; i < segments.length; i++) { - const sub = _boundSegment(segments[i], line.points, bounds); - if (sub.length) { - result.push(...sub); - } - } - return result; + const result = []; + const segments = line.segments; + + for (let i = 0; i < segments.length; i++) { + const sub = _boundSegment(segments[i], line.points, bounds); + if (sub.length) { + result.push(...sub); + } + } + return result; } /** * Find start and end index of a line. */ function findStartAndEnd(points, count, loop, spanGaps) { - let start = 0; - let end = count - 1; + let start = 0; + let end = count - 1; - if (loop && !spanGaps) { - // loop and not spaning gaps, first find a gap to start from - while (start < count && !points[start].skip) { - start++; - } - } + if (loop && !spanGaps) { + // loop and not spaning gaps, first find a gap to start from + while (start < count && !points[start].skip) { + start++; + } + } - // find first non skipped point (after the first gap possibly) - while (start < count && points[start].skip) { - start++; - } + // find first non skipped point (after the first gap possibly) + while (start < count && points[start].skip) { + start++; + } - // if we looped to count, start needs to be 0 - start %= count; + // if we looped to count, start needs to be 0 + start %= count; - if (loop) { - // loop will go past count, if start > 0 - end += start; - } + if (loop) { + // loop will go past count, if start > 0 + end += start; + } - while (end > start && points[end % count].skip) { - end--; - } + while (end > start && points[end % count].skip) { + end--; + } - // end could be more than count, normalize - end %= count; + // end could be more than count, normalize + end %= count; - return {start, end}; + return {start, end}; } /** @@ -186,35 +186,35 @@ function findStartAndEnd(points, count, loop, spanGaps) { * @param {boolean} loop - boolean indicating that this would be a loop if no gaps are found */ function solidSegments(points, start, max, loop) { - const count = points.length; - const result = []; - let last = start; - let prev = points[start]; - let end; - - for (end = start + 1; end <= max; ++end) { - const cur = points[end % count]; - if (cur.skip || cur.stop) { - if (!prev.skip) { - loop = false; - result.push({start: start % count, end: (end - 1) % count, loop}); - // @ts-ignore - start = last = cur.stop ? end : null; - } - } else { - last = end; - if (prev.skip) { - start = end; - } - } - prev = cur; - } - - if (last !== null) { - result.push({start: start % count, end: last % count, loop}); - } - - return result; + const count = points.length; + const result = []; + let last = start; + let prev = points[start]; + let end; + + for (end = start + 1; end <= max; ++end) { + const cur = points[end % count]; + if (cur.skip || cur.stop) { + if (!prev.skip) { + loop = false; + result.push({start: start % count, end: (end - 1) % count, loop}); + // @ts-ignore + start = last = cur.stop ? end : null; + } + } else { + last = end; + if (prev.skip) { + start = end; + } + } + prev = cur; + } + + if (last !== null) { + result.push({start: start % count, end: last % count, loop}); + } + + return result; } /** @@ -224,22 +224,22 @@ function solidSegments(points, start, max, loop) { * @private */ export function _computeSegments(line) { - const points = line.points; - const spanGaps = line.options.spanGaps; - const count = points.length; + const points = line.points; + const spanGaps = line.options.spanGaps; + const count = points.length; - if (!count) { - return []; - } + if (!count) { + return []; + } - const loop = !!line._loop; - const {start, end} = findStartAndEnd(points, count, loop, spanGaps); + const loop = !!line._loop; + const {start, end} = findStartAndEnd(points, count, loop, spanGaps); - if (spanGaps === true) { - return [{start, end, loop}]; - } + if (spanGaps === true) { + return [{start, end, loop}]; + } - const max = end < start ? end + count : end; - const completeLoop = !!line._fullLoop && start === 0 && end === count - 1; - return solidSegments(points, start, max, completeLoop); + const max = end < start ? end + count : end; + const completeLoop = !!line._fullLoop && start === 0 && end === count - 1; + return solidSegments(points, start, max, completeLoop); } diff --git a/src/index.js b/src/index.js index 6bc2f2f93d0..d3539015bf2 100644 --- a/src/index.js +++ b/src/index.js @@ -46,7 +46,7 @@ Object.assign(Chart, controllers, scales, elements, plugins, platforms); Chart.Chart = Chart; if (typeof window !== 'undefined') { - window.Chart = Chart; + window.Chart = Chart; } export default Chart; diff --git a/src/platform/platform.base.js b/src/platform/platform.base.js index d17731a78e6..5b5851ebab7 100644 --- a/src/platform/platform.base.js +++ b/src/platform/platform.base.js @@ -7,71 +7,71 @@ * Abstract class that allows abstracting platform dependencies away from the chart. */ export default class BasePlatform { - /** + /** * Called at chart construction time, returns a context2d instance implementing * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. * @param {HTMLCanvasElement} canvas - The canvas from which to acquire context (platform specific) * @param {object} options - The chart options */ - acquireContext(canvas, options) {} // eslint-disable-line no-unused-vars + acquireContext(canvas, options) {} // eslint-disable-line no-unused-vars - /** + /** * Called at chart destruction time, releases any resources associated to the context * previously returned by the acquireContext() method. * @param {CanvasRenderingContext2D} context - The context2d instance * @returns {boolean} true if the method succeeded, else false */ - releaseContext(context) { // eslint-disable-line no-unused-vars - return false; - } + releaseContext(context) { // eslint-disable-line no-unused-vars + return false; + } - /** + /** * Registers the specified listener on the given chart. * @param {Chart} chart - Chart from which to listen for event * @param {string} type - The ({@link ChartEvent}) type to listen for * @param {function} listener - Receives a notification (an object that implements * the {@link ChartEvent} interface) when an event of the specified type occurs. */ - addEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars + addEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars - /** + /** * Removes the specified listener previously registered with addEventListener. * @param {Chart} chart - Chart from which to remove the listener * @param {string} type - The ({@link ChartEvent}) type to remove * @param {function} listener - The listener function to remove from the event target. */ - removeEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars + removeEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars - /** + /** * @returns {number} the current devicePixelRatio of the device this platform is connected to. */ - getDevicePixelRatio() { - return 1; - } + getDevicePixelRatio() { + return 1; + } - /** + /** * Returns the maximum size in pixels of given canvas element. * @param {HTMLCanvasElement} element * @param {number} [width] - content width of parent element * @param {number} [height] - content height of parent element * @param {number} [aspectRatio] - aspect ratio to maintain */ - getMaximumSize(element, width, height, aspectRatio) { - width = Math.max(0, width || element.width); - height = height || element.height; - return { - width, - height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height) - }; - } + getMaximumSize(element, width, height, aspectRatio) { + width = Math.max(0, width || element.width); + height = height || element.height; + return { + width, + height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height) + }; + } - /** + /** * @param {HTMLCanvasElement} canvas * @returns {boolean} true if the canvas is attached to the platform, false if not. */ - isAttached(canvas) { // eslint-disable-line no-unused-vars - return true; - } + isAttached(canvas) { // eslint-disable-line no-unused-vars + return true; + } } /** diff --git a/src/platform/platform.basic.js b/src/platform/platform.basic.js index 681dc7a5157..7b38caf4a19 100644 --- a/src/platform/platform.basic.js +++ b/src/platform/platform.basic.js @@ -11,10 +11,10 @@ import BasePlatform from './platform.base'; * @extends BasePlatform */ export default class BasicPlatform extends BasePlatform { - acquireContext(item) { - // To prevent canvas fingerprinting, some add-ons undefine the getContext - // method, for example: https://github.com/kkapsner/CanvasBlocker - // https://github.com/chartjs/Chart.js/issues/2807 - return item && item.getContext && item.getContext('2d') || null; - } + acquireContext(item) { + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + return item && item.getContext && item.getContext('2d') || null; + } } diff --git a/src/platform/platform.dom.js b/src/platform/platform.dom.js index 24068924fef..1b6fb13f7e3 100644 --- a/src/platform/platform.dom.js +++ b/src/platform/platform.dom.js @@ -19,15 +19,15 @@ const EXPANDO_KEY = '$chartjs'; * @see https://developer.mozilla.org/en-US/docs/Web/Events */ const EVENT_TYPES = { - touchstart: 'mousedown', - touchmove: 'mousemove', - touchend: 'mouseup', - pointerenter: 'mouseenter', - pointerdown: 'mousedown', - pointermove: 'mousemove', - pointerup: 'mouseup', - pointerleave: 'mouseout', - pointerout: 'mouseout' + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' }; const isNullOrEmpty = value => value === null || value === ''; @@ -39,55 +39,55 @@ const isNullOrEmpty = value => value === null || value === ''; * @param {{ options: any; }} config */ function initCanvas(canvas, config) { - const style = canvas.style; - - // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it - // returns null or '' if no explicit value has been set to the canvas attribute. - const renderHeight = canvas.getAttribute('height'); - const renderWidth = canvas.getAttribute('width'); - - // Chart.js modifies some canvas values that we want to restore on destroy - canvas[EXPANDO_KEY] = { - initial: { - height: renderHeight, - width: renderWidth, - style: { - display: style.display, - height: style.height, - width: style.width - } - } - }; - - // Force canvas to display as block to avoid extra space caused by inline - // elements, which would interfere with the responsive resize process. - // https://github.com/chartjs/Chart.js/issues/2538 - style.display = style.display || 'block'; - // Include possible borders in the size - style.boxSizing = style.boxSizing || 'border-box'; - - if (isNullOrEmpty(renderWidth)) { - const displayWidth = readUsedSize(canvas, 'width'); - if (displayWidth !== undefined) { - canvas.width = displayWidth; - } - } - - if (isNullOrEmpty(renderHeight)) { - if (canvas.style.height === '') { - // If no explicit render height and style height, let's apply the aspect ratio, - // which one can be specified by the user but also by charts as default option - // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. - canvas.height = canvas.width / (config.options.aspectRatio || 2); - } else { - const displayHeight = readUsedSize(canvas, 'height'); - if (displayHeight !== undefined) { - canvas.height = displayHeight; - } - } - } - - return canvas; + const style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + const renderHeight = canvas.getAttribute('height'); + const renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + // Include possible borders in the size + style.boxSizing = style.boxSizing || 'border-box'; + + if (isNullOrEmpty(renderWidth)) { + const displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (isNullOrEmpty(renderHeight)) { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + const displayHeight = readUsedSize(canvas, 'height'); + if (displayHeight !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; } // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. @@ -95,160 +95,160 @@ function initCanvas(canvas, config) { const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; function addListener(node, type, listener) { - node.addEventListener(type, listener, eventListenerOptions); + node.addEventListener(type, listener, eventListenerOptions); } function removeListener(chart, type, listener) { - chart.canvas.removeEventListener(type, listener, eventListenerOptions); + chart.canvas.removeEventListener(type, listener, eventListenerOptions); } function fromNativeEvent(event, chart) { - const type = EVENT_TYPES[event.type] || event.type; - const {x, y} = getRelativePosition(event, chart); - return { - type, - chart, - native: event, - x: x !== undefined ? x : null, - y: y !== undefined ? y : null, - }; + const type = EVENT_TYPES[event.type] || event.type; + const {x, y} = getRelativePosition(event, chart); + return { + type, + chart, + native: event, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; } function createAttachObserver(chart, type, listener) { - const canvas = chart.canvas; - const container = canvas && _getParentNode(canvas); - const element = container || canvas; - const observer = new MutationObserver(entries => { - const parent = _getParentNode(element); - entries.forEach(entry => { - for (let i = 0; i < entry.addedNodes.length; i++) { - const added = entry.addedNodes[i]; - if (added === element || added === parent) { - listener(entry.target); - } - } - }); - }); - observer.observe(document, {childList: true, subtree: true}); - return observer; + const canvas = chart.canvas; + const container = canvas && _getParentNode(canvas); + const element = container || canvas; + const observer = new MutationObserver(entries => { + const parent = _getParentNode(element); + entries.forEach(entry => { + for (let i = 0; i < entry.addedNodes.length; i++) { + const added = entry.addedNodes[i]; + if (added === element || added === parent) { + listener(entry.target); + } + } + }); + }); + observer.observe(document, {childList: true, subtree: true}); + return observer; } function createDetachObserver(chart, type, listener) { - const canvas = chart.canvas; - const container = canvas && _getParentNode(canvas); - if (!container) { - return; - } - const observer = new MutationObserver(entries => { - entries.forEach(entry => { - for (let i = 0; i < entry.removedNodes.length; i++) { - if (entry.removedNodes[i] === canvas) { - listener(); - break; - } - } - }); - }); - observer.observe(container, {childList: true}); - return observer; + const canvas = chart.canvas; + const container = canvas && _getParentNode(canvas); + if (!container) { + return; + } + const observer = new MutationObserver(entries => { + entries.forEach(entry => { + for (let i = 0; i < entry.removedNodes.length; i++) { + if (entry.removedNodes[i] === canvas) { + listener(); + break; + } + } + }); + }); + observer.observe(container, {childList: true}); + return observer; } const drpListeningCharts = new Map(); let oldDevicePixelRatio = 0; function onWindowResize() { - const dpr = window.devicePixelRatio; - if (dpr === oldDevicePixelRatio) { - return; - } - oldDevicePixelRatio = dpr; - drpListeningCharts.forEach((resize, chart) => { - if (chart.currentDevicePixelRatio !== dpr) { - resize(); - } - }); + const dpr = window.devicePixelRatio; + if (dpr === oldDevicePixelRatio) { + return; + } + oldDevicePixelRatio = dpr; + drpListeningCharts.forEach((resize, chart) => { + if (chart.currentDevicePixelRatio !== dpr) { + resize(); + } + }); } function listenDevicePixelRatioChanges(chart, resize) { - if (!drpListeningCharts.size) { - window.addEventListener('resize', onWindowResize); - } - drpListeningCharts.set(chart, resize); + if (!drpListeningCharts.size) { + window.addEventListener('resize', onWindowResize); + } + drpListeningCharts.set(chart, resize); } function unlistenDevicePixelRatioChanges(chart) { - drpListeningCharts.delete(chart); - if (!drpListeningCharts.size) { - window.removeEventListener('resize', onWindowResize); - } + drpListeningCharts.delete(chart); + if (!drpListeningCharts.size) { + window.removeEventListener('resize', onWindowResize); + } } function createResizeObserver(chart, type, listener) { - const canvas = chart.canvas; - const container = canvas && _getParentNode(canvas); - if (!container) { - return; - } - const resize = throttled((width, height) => { - const w = container.clientWidth; - listener(width, height); - if (w < container.clientWidth) { - // If the container size shrank during chart resize, let's assume - // scrollbar appeared. So we resize again with the scrollbar visible - - // effectively making chart smaller and the scrollbar hidden again. - // Because we are inside `throttled`, and currently `ticking`, scroll - // events are ignored during this whole 2 resize process. - // If we assumed wrong and something else happened, we are resizing - // twice in a frame (potential performance issue) - listener(); - } - }, window); - - // @ts-ignore until https://github.com/microsoft/TypeScript/issues/37861 implemented - const observer = new ResizeObserver(entries => { - const entry = entries[0]; - const width = entry.contentRect.width; - const height = entry.contentRect.height; - // When its container's display is set to 'none' the callback will be called with a - // size of (0, 0), which will cause the chart to lost its original height, so skip - // resizing in such case. - if (width === 0 && height === 0) { - return; - } - resize(width, height); - }); - observer.observe(container); - listenDevicePixelRatioChanges(chart, resize); - - return observer; + const canvas = chart.canvas; + const container = canvas && _getParentNode(canvas); + if (!container) { + return; + } + const resize = throttled((width, height) => { + const w = container.clientWidth; + listener(width, height); + if (w < container.clientWidth) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(); + } + }, window); + + // @ts-ignore until https://github.com/microsoft/TypeScript/issues/37861 implemented + const observer = new ResizeObserver(entries => { + const entry = entries[0]; + const width = entry.contentRect.width; + const height = entry.contentRect.height; + // When its container's display is set to 'none' the callback will be called with a + // size of (0, 0), which will cause the chart to lost its original height, so skip + // resizing in such case. + if (width === 0 && height === 0) { + return; + } + resize(width, height); + }); + observer.observe(container); + listenDevicePixelRatioChanges(chart, resize); + + return observer; } function releaseObserver(chart, type, observer) { - if (observer) { - observer.disconnect(); - } - if (type === 'resize') { - unlistenDevicePixelRatioChanges(chart); - } + if (observer) { + observer.disconnect(); + } + if (type === 'resize') { + unlistenDevicePixelRatioChanges(chart); + } } function createProxyAndListen(chart, type, listener) { - const canvas = chart.canvas; - const proxy = throttled((event) => { - // This case can occur if the chart is destroyed while waiting - // for the throttled function to occur. We prevent crashes by checking - // for a destroyed chart - if (chart.ctx !== null) { - listener(fromNativeEvent(event, chart)); - } - }, chart, (args) => { - const event = args[0]; - return [event, event.offsetX, event.offsetY]; - }); - - addListener(canvas, type, proxy); - - return proxy; + const canvas = chart.canvas; + const proxy = throttled((event) => { + // This case can occur if the chart is destroyed while waiting + // for the throttled function to occur. We prevent crashes by checking + // for a destroyed chart + if (chart.ctx !== null) { + listener(fromNativeEvent(event, chart)); + } + }, chart, (args) => { + const event = args[0]; + return [event, event.offsetX, event.offsetY]; + }); + + addListener(canvas, type, proxy); + + return proxy; } /** @@ -257,131 +257,131 @@ function createProxyAndListen(chart, type, listener) { */ export default class DomPlatform extends BasePlatform { - /** + /** * @param {HTMLCanvasElement} canvas * @param {{ options: { aspectRatio?: number; }; }} config * @return {CanvasRenderingContext2D|null} */ - acquireContext(canvas, config) { - // To prevent canvas fingerprinting, some add-ons undefine the getContext - // method, for example: https://github.com/kkapsner/CanvasBlocker - // https://github.com/chartjs/Chart.js/issues/2807 - const context = canvas && canvas.getContext && canvas.getContext('2d'); - - // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the canvas is - // inside an iframe or when running in a protected environment. We could guess the - // types from their toString() value but let's keep things flexible and assume it's - // a sufficient condition if the canvas has a context2D which has canvas as `canvas`. - // https://github.com/chartjs/Chart.js/issues/3887 - // https://github.com/chartjs/Chart.js/issues/4102 - // https://github.com/chartjs/Chart.js/issues/4152 - if (context && context.canvas === canvas) { - // Load platform resources on first chart creation, to make it possible to - // import the library before setting platform options. - initCanvas(canvas, config); - return context; - } - - return null; - } - - /** + acquireContext(canvas, config) { + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + const context = canvas && canvas.getContext && canvas.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the canvas is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the canvas has a context2D which has canvas as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === canvas) { + // Load platform resources on first chart creation, to make it possible to + // import the library before setting platform options. + initCanvas(canvas, config); + return context; + } + + return null; + } + + /** * @param {CanvasRenderingContext2D} context */ - releaseContext(context) { - const canvas = context.canvas; - if (!canvas[EXPANDO_KEY]) { - return false; - } - - const initial = canvas[EXPANDO_KEY].initial; - ['height', 'width'].forEach((prop) => { - const value = initial[prop]; - if (isNullOrUndef(value)) { - canvas.removeAttribute(prop); - } else { - canvas.setAttribute(prop, value); - } - }); - - const style = initial.style || {}; - Object.keys(style).forEach((key) => { - canvas.style[key] = style[key]; - }); - - // The canvas render size might have been changed (and thus the state stack discarded), - // we can't use save() and restore() to restore the initial state. So make sure that at - // least the canvas context is reset to the default state by setting the canvas width. - // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html - // eslint-disable-next-line no-self-assign - canvas.width = canvas.width; - - delete canvas[EXPANDO_KEY]; - return true; - } - - /** + releaseContext(context) { + const canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return false; + } + + const initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach((prop) => { + const value = initial[prop]; + if (isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + const style = initial.style || {}; + Object.keys(style).forEach((key) => { + canvas.style[key] = style[key]; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + // eslint-disable-next-line no-self-assign + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + return true; + } + + /** * * @param {Chart} chart * @param {string} type * @param {function} listener */ - addEventListener(chart, type, listener) { - // Can have only one listener per type, so make sure previous is removed - this.removeEventListener(chart, type); - - const proxies = chart.$proxies || (chart.$proxies = {}); - const handlers = { - attach: createAttachObserver, - detach: createDetachObserver, - resize: createResizeObserver - }; - const handler = handlers[type] || createProxyAndListen; - proxies[type] = handler(chart, type, listener); - } - - - /** + addEventListener(chart, type, listener) { + // Can have only one listener per type, so make sure previous is removed + this.removeEventListener(chart, type); + + const proxies = chart.$proxies || (chart.$proxies = {}); + const handlers = { + attach: createAttachObserver, + detach: createDetachObserver, + resize: createResizeObserver + }; + const handler = handlers[type] || createProxyAndListen; + proxies[type] = handler(chart, type, listener); + } + + + /** * @param {Chart} chart * @param {string} type */ - removeEventListener(chart, type) { - const proxies = chart.$proxies || (chart.$proxies = {}); - const proxy = proxies[type]; - - if (!proxy) { - return; - } - - const handlers = { - attach: releaseObserver, - detach: releaseObserver, - resize: releaseObserver - }; - const handler = handlers[type] || removeListener; - handler(chart, type, proxy); - proxies[type] = undefined; - } - - getDevicePixelRatio() { - return window.devicePixelRatio; - } - - /** + removeEventListener(chart, type) { + const proxies = chart.$proxies || (chart.$proxies = {}); + const proxy = proxies[type]; + + if (!proxy) { + return; + } + + const handlers = { + attach: releaseObserver, + detach: releaseObserver, + resize: releaseObserver + }; + const handler = handlers[type] || removeListener; + handler(chart, type, proxy); + proxies[type] = undefined; + } + + getDevicePixelRatio() { + return window.devicePixelRatio; + } + + /** * @param {HTMLCanvasElement} canvas * @param {number} [width] - content width of parent element * @param {number} [height] - content height of parent element * @param {number} [aspectRatio] - aspect ratio to maintain */ - getMaximumSize(canvas, width, height, aspectRatio) { - return getMaximumSize(canvas, width, height, aspectRatio); - } + getMaximumSize(canvas, width, height, aspectRatio) { + return getMaximumSize(canvas, width, height, aspectRatio); + } - /** + /** * @param {HTMLCanvasElement} canvas */ - isAttached(canvas) { - const container = _getParentNode(canvas); - return !!(container && _getParentNode(container)); - } + isAttached(canvas) { + const container = _getParentNode(canvas); + return !!(container && _getParentNode(container)); + } } diff --git a/src/plugins/plugin.decimation.js b/src/plugins/plugin.decimation.js index 4093e365679..c320d2f82f5 100644 --- a/src/plugins/plugin.decimation.js +++ b/src/plugins/plugin.decimation.js @@ -1,135 +1,135 @@ import {isNullOrUndef, resolve} from '../helpers'; function minMaxDecimation(data, availableWidth) { - let i, point, x, y, prevX, minIndex, maxIndex, minY, maxY; - const decimated = []; - - const xMin = data[0].x; - const xMax = data[data.length - 1].x; - const dx = xMax - xMin; - - for (i = 0; i < data.length; ++i) { - point = data[i]; - x = (point.x - xMin) / dx * availableWidth; - y = point.y; - const truncX = x | 0; - - if (truncX === prevX) { - // Determine `minY` / `maxY` and `avgX` while we stay within same x-position - if (y < minY) { - minY = y; - minIndex = i; - } else if (y > maxY) { - maxY = y; - maxIndex = i; - } - } else { - // Push up to 4 points, 3 for the last interval and the first point for this interval - if (minIndex && maxIndex) { - decimated.push(data[minIndex], data[maxIndex]); - } - if (i > 0) { - // Last point in the previous interval - decimated.push(data[i - 1]); - } - decimated.push(point); - prevX = truncX; - minY = maxY = y; - minIndex = maxIndex = i; - } - } - - return decimated; + let i, point, x, y, prevX, minIndex, maxIndex, minY, maxY; + const decimated = []; + + const xMin = data[0].x; + const xMax = data[data.length - 1].x; + const dx = xMax - xMin; + + for (i = 0; i < data.length; ++i) { + point = data[i]; + x = (point.x - xMin) / dx * availableWidth; + y = point.y; + const truncX = x | 0; + + if (truncX === prevX) { + // Determine `minY` / `maxY` and `avgX` while we stay within same x-position + if (y < minY) { + minY = y; + minIndex = i; + } else if (y > maxY) { + maxY = y; + maxIndex = i; + } + } else { + // Push up to 4 points, 3 for the last interval and the first point for this interval + if (minIndex && maxIndex) { + decimated.push(data[minIndex], data[maxIndex]); + } + if (i > 0) { + // Last point in the previous interval + decimated.push(data[i - 1]); + } + decimated.push(point); + prevX = truncX; + minY = maxY = y; + minIndex = maxIndex = i; + } + } + + return decimated; } export default { - id: 'decimation', - - defaults: { - algorithm: 'min-max', - enabled: false, - }, - - beforeElementsUpdate: (chart, args, options) => { - if (!options.enabled) { - return; - } - - // Assume the entire chart is available to show a few more points than needed - const availableWidth = chart.width; - - chart.data.datasets.forEach((dataset, datasetIndex) => { - const {_data, indexAxis} = dataset; - const meta = chart.getDatasetMeta(datasetIndex); - const data = _data || dataset.data; - - if (resolve([indexAxis, chart.options.indexAxis]) === 'y') { - // Decimation is only supported for lines that have an X indexAxis - return; - } - - if (meta.type !== 'line') { - // Only line datasets are supported - return; - } - - const xAxis = chart.scales[meta.xAxisID]; - if (xAxis.type !== 'linear' && xAxis.type !== 'time') { - // Only linear interpolation is supported - return; - } - - if (chart.options.parsing) { - // Plugin only supports data that does not need parsing - return; - } - - if (data.length <= 4 * availableWidth) { - // No decimation is required until we are above this threshold - return; - } - - if (isNullOrUndef(_data)) { - // First time we are seeing this dataset - // We override the 'data' property with a setter that stores the - // raw data in _data, but reads the decimated data from _decimated - // TODO: Undo this on chart destruction - dataset._data = data; - delete dataset.data; - Object.defineProperty(dataset, 'data', { - configurable: true, - enumerable: true, - get: function() { - return this._decimated; - }, - set: function(d) { - this._data = d; - } - }); - } - - // Point the chart to the decimated data - let decimated; - switch (options.algorithm) { - case 'min-max': - decimated = minMaxDecimation(data, availableWidth); - break; - default: - throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`); - } - - dataset._decimated = decimated; - }); - }, - - destroy(chart) { - chart.data.datasets.forEach((dataset) => { - if (dataset._decimated) { - const data = dataset._data; - delete dataset._decimated; - delete dataset._data; - Object.defineProperty(dataset, 'data', {value: data}); - } - }); - } + id: 'decimation', + + defaults: { + algorithm: 'min-max', + enabled: false, + }, + + beforeElementsUpdate: (chart, args, options) => { + if (!options.enabled) { + return; + } + + // Assume the entire chart is available to show a few more points than needed + const availableWidth = chart.width; + + chart.data.datasets.forEach((dataset, datasetIndex) => { + const {_data, indexAxis} = dataset; + const meta = chart.getDatasetMeta(datasetIndex); + const data = _data || dataset.data; + + if (resolve([indexAxis, chart.options.indexAxis]) === 'y') { + // Decimation is only supported for lines that have an X indexAxis + return; + } + + if (meta.type !== 'line') { + // Only line datasets are supported + return; + } + + const xAxis = chart.scales[meta.xAxisID]; + if (xAxis.type !== 'linear' && xAxis.type !== 'time') { + // Only linear interpolation is supported + return; + } + + if (chart.options.parsing) { + // Plugin only supports data that does not need parsing + return; + } + + if (data.length <= 4 * availableWidth) { + // No decimation is required until we are above this threshold + return; + } + + if (isNullOrUndef(_data)) { + // First time we are seeing this dataset + // We override the 'data' property with a setter that stores the + // raw data in _data, but reads the decimated data from _decimated + // TODO: Undo this on chart destruction + dataset._data = data; + delete dataset.data; + Object.defineProperty(dataset, 'data', { + configurable: true, + enumerable: true, + get: function() { + return this._decimated; + }, + set: function(d) { + this._data = d; + } + }); + } + + // Point the chart to the decimated data + let decimated; + switch (options.algorithm) { + case 'min-max': + decimated = minMaxDecimation(data, availableWidth); + break; + default: + throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`); + } + + dataset._decimated = decimated; + }); + }, + + destroy(chart) { + chart.data.datasets.forEach((dataset) => { + if (dataset._decimated) { + const data = dataset._data; + delete dataset._decimated; + delete dataset._data; + Object.defineProperty(dataset, 'data', {value: data}); + } + }); + } }; diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 572d74acdab..9b725b3f251 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -21,31 +21,31 @@ import {TAU, _normalizeAngle} from '../helpers/helpers.math'; * @param {number} index */ function getLineByIndex(chart, index) { - const meta = chart.getDatasetMeta(index); - const visible = meta && chart.isDatasetVisible(index); - return visible ? meta.dataset : null; + const meta = chart.getDatasetMeta(index); + const visible = meta && chart.isDatasetVisible(index); + return visible ? meta.dataset : null; } /** * @param {LineElement} line */ function parseFillOption(line) { - const options = line.options; - const fillOption = options.fill; - let fill = valueOrDefault(fillOption && fillOption.target, fillOption); - - if (fill === undefined) { - fill = !!options.backgroundColor; - } - - if (fill === false || fill === null) { - return false; - } - - if (fill === true) { - return 'origin'; - } - return fill; + const options = line.options; + const fillOption = options.fill; + let fill = valueOrDefault(fillOption && fillOption.target, fillOption); + + if (fill === undefined) { + fill = !!options.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + return fill; } /** @@ -54,146 +54,146 @@ function parseFillOption(line) { * @param {number} count */ function decodeFill(line, index, count) { - const fill = parseFillOption(line); + const fill = parseFillOption(line); - if (isObject(fill)) { - return isNaN(fill.value) ? false : fill; - } + if (isObject(fill)) { + return isNaN(fill.value) ? false : fill; + } - let target = parseFloat(fill); + let target = parseFloat(fill); - if (isFinite(target) && Math.floor(target) === target) { - if (fill[0] === '-' || fill[0] === '+') { - target = index + target; - } + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } - if (target === index || target < 0 || target >= count) { - return false; - } + if (target === index || target < 0 || target >= count) { + return false; + } - return target; - } + return target; + } - return ['origin', 'start', 'end', 'stack'].indexOf(fill) >= 0 && fill; + return ['origin', 'start', 'end', 'stack'].indexOf(fill) >= 0 && fill; } function computeLinearBoundary(source) { - const {scale = {}, fill} = source; - let target = null; - let horizontal; - - if (fill === 'start') { - target = scale.bottom; - } else if (fill === 'end') { - target = scale.top; - } else if (isObject(fill)) { - target = scale.getPixelForValue(fill.value); - } else if (scale.getBasePixel) { - target = scale.getBasePixel(); - } - - if (isFinite(target)) { - horizontal = scale.isHorizontal(); - return { - x: horizontal ? target : null, - y: horizontal ? null : target - }; - } - - return null; + const {scale = {}, fill} = source; + let target = null; + let horizontal; + + if (fill === 'start') { + target = scale.bottom; + } else if (fill === 'end') { + target = scale.top; + } else if (isObject(fill)) { + target = scale.getPixelForValue(fill.value); + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + + return null; } // TODO: use elements.ArcElement instead class simpleArc { - constructor(opts) { - this.x = opts.x; - this.y = opts.y; - this.radius = opts.radius; - } - - pathSegment(ctx, bounds, opts) { - const {x, y, radius} = this; - bounds = bounds || {start: 0, end: TAU}; - if (opts.reverse) { - ctx.arc(x, y, radius, bounds.end, bounds.start, true); - } else { - ctx.arc(x, y, radius, bounds.start, bounds.end); - } - return !opts.bounds; - } - - interpolate(point, property) { - const {x, y, radius} = this; - const angle = point.angle; - if (property === 'angle') { - return { - x: x + Math.cos(angle) * radius, - y: y + Math.sin(angle) * radius, - angle - }; - } - } + constructor(opts) { + this.x = opts.x; + this.y = opts.y; + this.radius = opts.radius; + } + + pathSegment(ctx, bounds, opts) { + const {x, y, radius} = this; + bounds = bounds || {start: 0, end: TAU}; + if (opts.reverse) { + ctx.arc(x, y, radius, bounds.end, bounds.start, true); + } else { + ctx.arc(x, y, radius, bounds.start, bounds.end); + } + return !opts.bounds; + } + + interpolate(point, property) { + const {x, y, radius} = this; + const angle = point.angle; + if (property === 'angle') { + return { + x: x + Math.cos(angle) * radius, + y: y + Math.sin(angle) * radius, + angle + }; + } + } } function computeCircularBoundary(source) { - const {scale, fill} = source; - const options = scale.options; - const length = scale.getLabels().length; - const target = []; - const start = options.reverse ? scale.max : scale.min; - const end = options.reverse ? scale.min : scale.max; - let i, center, value; - - if (fill === 'start') { - value = start; - } else if (fill === 'end') { - value = end; - } else if (isObject(fill)) { - value = fill.value; - } else { - value = scale.getBaseValue(); - } - - if (options.gridLines.circular) { - center = scale.getPointPositionForValue(0, start); - return new simpleArc({ - x: center.x, - y: center.y, - radius: scale.getDistanceFromCenterForValue(value) - }); - } - - for (i = 0; i < length; ++i) { - target.push(scale.getPointPositionForValue(i, value)); - } - return target; + const {scale, fill} = source; + const options = scale.options; + const length = scale.getLabels().length; + const target = []; + const start = options.reverse ? scale.max : scale.min; + const end = options.reverse ? scale.min : scale.max; + let i, center, value; + + if (fill === 'start') { + value = start; + } else if (fill === 'end') { + value = end; + } else if (isObject(fill)) { + value = fill.value; + } else { + value = scale.getBaseValue(); + } + + if (options.gridLines.circular) { + center = scale.getPointPositionForValue(0, start); + return new simpleArc({ + x: center.x, + y: center.y, + radius: scale.getDistanceFromCenterForValue(value) + }); + } + + for (i = 0; i < length; ++i) { + target.push(scale.getPointPositionForValue(i, value)); + } + return target; } function computeBoundary(source) { - const scale = source.scale || {}; + const scale = source.scale || {}; - if (scale.getPointPositionForValue) { - return computeCircularBoundary(source); - } - return computeLinearBoundary(source); + if (scale.getPointPositionForValue) { + return computeCircularBoundary(source); + } + return computeLinearBoundary(source); } function pointsFromSegments(boundary, line) { - const {x = null, y = null} = boundary || {}; - const linePoints = line.points; - const points = []; - line.segments.forEach((segment) => { - const first = linePoints[segment.start]; - const last = linePoints[segment.end]; - if (y !== null) { - points.push({x: first.x, y}); - points.push({x: last.x, y}); - } else if (x !== null) { - points.push({x, y: first.y}); - points.push({x, y: last.y}); - } - }); - return points; + const {x = null, y = null} = boundary || {}; + const linePoints = line.points; + const points = []; + line.segments.forEach((segment) => { + const first = linePoints[segment.start]; + const last = linePoints[segment.end]; + if (y !== null) { + points.push({x: first.x, y}); + points.push({x: last.x, y}); + } else if (x !== null) { + points.push({x, y: first.y}); + points.push({x, y: last.y}); + } + }); + return points; } /** @@ -201,20 +201,20 @@ function pointsFromSegments(boundary, line) { * @return {LineElement} */ function buildStackLine(source) { - const {chart, scale, index, line} = source; - const points = []; - const segments = line.segments; - const sourcePoints = line.points; - const linesBelow = getLinesBelow(chart, index); - linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line)); - - for (let i = 0; i < segments.length; i++) { - const segment = segments[i]; - for (let j = segment.start; j <= segment.end; j++) { - addPointsBelow(points, sourcePoints[j], linesBelow); - } - } - return new LineElement({points, options: {}}); + const {chart, scale, index, line} = source; + const points = []; + const segments = line.segments; + const sourcePoints = line.points; + const linesBelow = getLinesBelow(chart, index); + linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line)); + + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + for (let j = segment.start; j <= segment.end; j++) { + addPointsBelow(points, sourcePoints[j], linesBelow); + } + } + return new LineElement({points, options: {}}); } const isLineAndNotInHideAnimation = (meta) => meta.type === 'line' && !meta.hidden; @@ -225,19 +225,19 @@ const isLineAndNotInHideAnimation = (meta) => meta.type === 'line' && !meta.hidd * @return {LineElement[]} */ function getLinesBelow(chart, index) { - const below = []; - const metas = chart.getSortedVisibleDatasetMetas(); - - for (let i = 0; i < metas.length; i++) { - const meta = metas[i]; - if (meta.index === index) { - break; - } - if (isLineAndNotInHideAnimation(meta)) { - below.unshift(meta.dataset); - } - } - return below; + const below = []; + const metas = chart.getSortedVisibleDatasetMetas(); + + for (let i = 0; i < metas.length; i++) { + const meta = metas[i]; + if (meta.index === index) { + break; + } + if (isLineAndNotInHideAnimation(meta)) { + below.unshift(meta.dataset); + } + } + return below; } /** @@ -246,27 +246,27 @@ function getLinesBelow(chart, index) { * @param {LineElement[]} linesBelow */ function addPointsBelow(points, sourcePoint, linesBelow) { - const postponed = []; - for (let j = 0; j < linesBelow.length; j++) { - const line = linesBelow[j]; - const {first, last, point} = findPoint(line, sourcePoint, 'x'); - - if (!point || (first && last)) { - continue; - } - if (first) { - // First point of an segment -> need to add another point before this, - // from next line below. - postponed.unshift(point); - } else { - points.push(point); - if (!last) { - // In the middle of an segment, no need to add more points. - break; - } - } - } - points.push(...postponed); + const postponed = []; + for (let j = 0; j < linesBelow.length; j++) { + const line = linesBelow[j]; + const {first, last, point} = findPoint(line, sourcePoint, 'x'); + + if (!point || (first && last)) { + continue; + } + if (first) { + // First point of an segment -> need to add another point before this, + // from next line below. + postponed.unshift(point); + } else { + points.push(point); + if (!last) { + // In the middle of an segment, no need to add more points. + break; + } + } + } + points.push(...postponed); } /** @@ -276,47 +276,47 @@ function addPointsBelow(points, sourcePoint, linesBelow) { * @returns {{point?: PointElement, first?: boolean, last?: boolean}} */ function findPoint(line, sourcePoint, property) { - const point = line.interpolate(sourcePoint, property); - if (!point) { - return {}; - } - - const pointValue = point[property]; - const segments = line.segments; - const linePoints = line.points; - let first = false; - let last = false; - for (let i = 0; i < segments.length; i++) { - const segment = segments[i]; - const firstValue = linePoints[segment.start][property]; - const lastValue = linePoints[segment.end][property]; - if (pointValue >= firstValue && pointValue <= lastValue) { - first = pointValue === firstValue; - last = pointValue === lastValue; - break; - } - } - return {first, last, point}; + const point = line.interpolate(sourcePoint, property); + if (!point) { + return {}; + } + + const pointValue = point[property]; + const segments = line.segments; + const linePoints = line.points; + let first = false; + let last = false; + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + const firstValue = linePoints[segment.start][property]; + const lastValue = linePoints[segment.end][property]; + if (pointValue >= firstValue && pointValue <= lastValue) { + first = pointValue === firstValue; + last = pointValue === lastValue; + break; + } + } + return {first, last, point}; } function getTarget(source) { - const {chart, fill, line} = source; + const {chart, fill, line} = source; - if (isFinite(fill)) { - return getLineByIndex(chart, fill); - } + if (isFinite(fill)) { + return getLineByIndex(chart, fill); + } - if (fill === 'stack') { - return buildStackLine(source); - } + if (fill === 'stack') { + return buildStackLine(source); + } - const boundary = computeBoundary(source); + const boundary = computeBoundary(source); - if (boundary instanceof simpleArc) { - return boundary; - } + if (boundary instanceof simpleArc) { + return boundary; + } - return createBoundaryLine(boundary, line); + return createBoundaryLine(boundary, line); } /** @@ -325,279 +325,279 @@ function getTarget(source) { * @return {LineElement?} */ function createBoundaryLine(boundary, line) { - let points = []; - let _loop = false; - - if (isArray(boundary)) { - _loop = true; - // @ts-ignore - points = boundary; - } else { - points = pointsFromSegments(boundary, line); - } - - return points.length ? new LineElement({ - points, - options: {tension: 0}, - _loop, - _fullLoop: _loop - }) : null; + let points = []; + let _loop = false; + + if (isArray(boundary)) { + _loop = true; + // @ts-ignore + points = boundary; + } else { + points = pointsFromSegments(boundary, line); + } + + return points.length ? new LineElement({ + points, + options: {tension: 0}, + _loop, + _fullLoop: _loop + }) : null; } function resolveTarget(sources, index, propagate) { - const source = sources[index]; - let fill = source.fill; - const visited = [index]; - let target; - - if (!propagate) { - return fill; - } - - while (fill !== false && visited.indexOf(fill) === -1) { - if (!isFinite(fill)) { - return fill; - } - - target = sources[fill]; - if (!target) { - return false; - } - - if (target.visible) { - return fill; - } - - visited.push(fill); - fill = target.fill; - } - - return false; + const source = sources[index]; + let fill = source.fill; + const visited = [index]; + let target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; } function _clip(ctx, target, clipY) { - ctx.beginPath(); - target.path(ctx); - ctx.lineTo(target.last().x, clipY); - ctx.lineTo(target.first().x, clipY); - ctx.closePath(); - ctx.clip(); + ctx.beginPath(); + target.path(ctx); + ctx.lineTo(target.last().x, clipY); + ctx.lineTo(target.first().x, clipY); + ctx.closePath(); + ctx.clip(); } function getBounds(property, first, last, loop) { - if (loop) { - return; - } - let start = first[property]; - let end = last[property]; - - if (property === 'angle') { - start = _normalizeAngle(start); - end = _normalizeAngle(end); - } - return {property, start, end}; + if (loop) { + return; + } + let start = first[property]; + let end = last[property]; + + if (property === 'angle') { + start = _normalizeAngle(start); + end = _normalizeAngle(end); + } + return {property, start, end}; } function _getEdge(a, b, prop, fn) { - if (a && b) { - return fn(a[prop], b[prop]); - } - return a ? a[prop] : b ? b[prop] : 0; + if (a && b) { + return fn(a[prop], b[prop]); + } + return a ? a[prop] : b ? b[prop] : 0; } function _segments(line, target, property) { - const segments = line.segments; - const points = line.points; - const tpoints = target.points; - const parts = []; - - for (let i = 0; i < segments.length; i++) { - const segment = segments[i]; - const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop); - - if (!target.segments) { - // Special case for boundary not supporting `segments` (simpleArc) - // Bounds are provided as `target` for partial circle, or undefined for full circle - parts.push({ - source: segment, - target: bounds, - start: points[segment.start], - end: points[segment.end] - }); - continue; - } - - // Get all segments from `target` that intersect the bounds of current segment of `line` - const subs = _boundSegments(target, bounds); - - for (let j = 0; j < subs.length; ++j) { - const sub = subs[j]; - const subBounds = getBounds(property, tpoints[sub.start], tpoints[sub.end], sub.loop); - const fillSources = _boundSegment(segment, points, subBounds); - - for (let k = 0; k < fillSources.length; k++) { - parts.push({ - source: fillSources[k], - target: sub, - start: { - [property]: _getEdge(bounds, subBounds, 'start', Math.max) - }, - end: { - [property]: _getEdge(bounds, subBounds, 'end', Math.min) - } - - }); - } - } - } - return parts; + const segments = line.segments; + const points = line.points; + const tpoints = target.points; + const parts = []; + + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop); + + if (!target.segments) { + // Special case for boundary not supporting `segments` (simpleArc) + // Bounds are provided as `target` for partial circle, or undefined for full circle + parts.push({ + source: segment, + target: bounds, + start: points[segment.start], + end: points[segment.end] + }); + continue; + } + + // Get all segments from `target` that intersect the bounds of current segment of `line` + const subs = _boundSegments(target, bounds); + + for (let j = 0; j < subs.length; ++j) { + const sub = subs[j]; + const subBounds = getBounds(property, tpoints[sub.start], tpoints[sub.end], sub.loop); + const fillSources = _boundSegment(segment, points, subBounds); + + for (let k = 0; k < fillSources.length; k++) { + parts.push({ + source: fillSources[k], + target: sub, + start: { + [property]: _getEdge(bounds, subBounds, 'start', Math.max) + }, + end: { + [property]: _getEdge(bounds, subBounds, 'end', Math.min) + } + + }); + } + } + } + return parts; } function clipBounds(ctx, scale, bounds) { - const {top, bottom} = scale.chart.chartArea; - const {property, start, end} = bounds || {}; - if (property === 'x') { - ctx.beginPath(); - ctx.rect(start, top, end - start, bottom - top); - ctx.clip(); - } + const {top, bottom} = scale.chart.chartArea; + const {property, start, end} = bounds || {}; + if (property === 'x') { + ctx.beginPath(); + ctx.rect(start, top, end - start, bottom - top); + ctx.clip(); + } } function interpolatedLineTo(ctx, target, point, property) { - const interpolatedPoint = target.interpolate(point, property); - if (interpolatedPoint) { - ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y); - } + const interpolatedPoint = target.interpolate(point, property); + if (interpolatedPoint) { + ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y); + } } function _fill(ctx, cfg) { - const {line, target, property, color, scale} = cfg; - const segments = _segments(line, target, property); + const {line, target, property, color, scale} = cfg; + const segments = _segments(line, target, property); - ctx.fillStyle = color; - for (let i = 0, ilen = segments.length; i < ilen; ++i) { - const {source: src, target: tgt, start, end} = segments[i]; + ctx.fillStyle = color; + for (let i = 0, ilen = segments.length; i < ilen; ++i) { + const {source: src, target: tgt, start, end} = segments[i]; - ctx.save(); + ctx.save(); - clipBounds(ctx, scale, getBounds(property, start, end)); + clipBounds(ctx, scale, getBounds(property, start, end)); - ctx.beginPath(); + ctx.beginPath(); - const lineLoop = !!line.pathSegment(ctx, src); - if (lineLoop) { - ctx.closePath(); - } else { - interpolatedLineTo(ctx, target, end, property); - } + const lineLoop = !!line.pathSegment(ctx, src); + if (lineLoop) { + ctx.closePath(); + } else { + interpolatedLineTo(ctx, target, end, property); + } - const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true}); - const loop = lineLoop && targetLoop; - if (!loop) { - interpolatedLineTo(ctx, target, start, property); - } + const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true}); + const loop = lineLoop && targetLoop; + if (!loop) { + interpolatedLineTo(ctx, target, start, property); + } - ctx.closePath(); - ctx.fill(loop ? 'evenodd' : 'nonzero'); + ctx.closePath(); + ctx.fill(loop ? 'evenodd' : 'nonzero'); - ctx.restore(); - } + ctx.restore(); + } } function doFill(ctx, cfg) { - const {line, target, above, below, area, scale} = cfg; - const property = line._loop ? 'angle' : 'x'; + const {line, target, above, below, area, scale} = cfg; + const property = line._loop ? 'angle' : 'x'; - ctx.save(); + ctx.save(); - if (property === 'x' && below !== above) { - _clip(ctx, target, area.top); - _fill(ctx, {line, target, color: above, scale, property}); - ctx.restore(); - ctx.save(); - _clip(ctx, target, area.bottom); - } - _fill(ctx, {line, target, color: below, scale, property}); + if (property === 'x' && below !== above) { + _clip(ctx, target, area.top); + _fill(ctx, {line, target, color: above, scale, property}); + ctx.restore(); + ctx.save(); + _clip(ctx, target, area.bottom); + } + _fill(ctx, {line, target, color: below, scale, property}); - ctx.restore(); + ctx.restore(); } export default { - id: 'filler', - - afterDatasetsUpdate(chart, _args, options) { - const count = (chart.data.datasets || []).length; - const propagate = options.propagate; - const sources = []; - let meta, i, line, source; - - for (i = 0; i < count; ++i) { - meta = chart.getDatasetMeta(i); - line = meta.dataset; - source = null; - - if (line && line.options && line instanceof LineElement) { - source = { - visible: chart.isDatasetVisible(i), - index: i, - fill: decodeFill(line, i, count), - chart, - scale: meta.vScale, - line - }; - } - - meta.$filler = source; - sources.push(source); - } - - for (i = 0; i < count; ++i) { - source = sources[i]; - if (!source || source.fill === false) { - continue; - } - - source.fill = resolveTarget(sources, i, propagate); - } - }, - - beforeDatasetsDraw(chart) { - const metasets = chart.getSortedVisibleDatasetMetas(); - const area = chart.chartArea; - let i, meta; - - for (i = metasets.length - 1; i >= 0; --i) { - meta = metasets[i].$filler; - - if (meta) { - meta.line.updateControlPoints(area); - } - } - }, - - beforeDatasetDraw(chart, args) { - const area = chart.chartArea; - const ctx = chart.ctx; - const source = args.meta.$filler; - - if (!source || source.fill === false) { - return; - } - - const target = getTarget(source); - const {line, scale} = source; - const lineOpts = line.options; - const fillOption = lineOpts.fill; - const color = lineOpts.backgroundColor; - const {above = color, below = color} = fillOption || {}; - if (target && line.points.length) { - clipArea(ctx, area); - doFill(ctx, {line, target, above, below, area, scale}); - unclipArea(ctx); - } - }, - - defaults: { - propagate: true - } + id: 'filler', + + afterDatasetsUpdate(chart, _args, options) { + const count = (chart.data.datasets || []).length; + const propagate = options.propagate; + const sources = []; + let meta, i, line, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + line = meta.dataset; + source = null; + + if (line && line.options && line instanceof LineElement) { + source = { + visible: chart.isDatasetVisible(i), + index: i, + fill: decodeFill(line, i, count), + chart, + scale: meta.vScale, + line + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source || source.fill === false) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + } + }, + + beforeDatasetsDraw(chart) { + const metasets = chart.getSortedVisibleDatasetMetas(); + const area = chart.chartArea; + let i, meta; + + for (i = metasets.length - 1; i >= 0; --i) { + meta = metasets[i].$filler; + + if (meta) { + meta.line.updateControlPoints(area); + } + } + }, + + beforeDatasetDraw(chart, args) { + const area = chart.chartArea; + const ctx = chart.ctx; + const source = args.meta.$filler; + + if (!source || source.fill === false) { + return; + } + + const target = getTarget(source); + const {line, scale} = source; + const lineOpts = line.options; + const fillOption = lineOpts.fill; + const color = lineOpts.backgroundColor; + const {above = color, below = color} = fillOption || {}; + if (target && line.points.length) { + clipArea(ctx, area); + doFill(ctx, {line, target, above, below, area, scale}); + unclipArea(ctx); + } + }, + + defaults: { + propagate: true + } }; diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index bf32e359a6b..af3eaef5b32 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -3,9 +3,9 @@ import Element from '../core/core.element'; import layouts from '../core/core.layouts'; import {drawPoint, renderText} from '../helpers/helpers.canvas'; import { - callback as call, valueOrDefault, toFont, isObject, - toPadding, getRtlAdapter, overrideTextDirection, restoreTextDirection, - clipArea, unclipArea + callback as call, valueOrDefault, toFont, isObject, + toPadding, getRtlAdapter, overrideTextDirection, restoreTextDirection, + clipArea, unclipArea } from '../helpers/index'; import {_toLeftRightCenter, _alignStartEnd} from '../helpers/helpers.extras'; /** @@ -13,606 +13,606 @@ import {_toLeftRightCenter, _alignStartEnd} from '../helpers/helpers.extras'; */ const getBoxSize = (labelOpts, fontSize) => { - let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts; - - if (labelOpts.usePointStyle) { - boxHeight = Math.min(boxHeight, fontSize); - boxWidth = Math.min(boxWidth, fontSize); - } - - return { - boxWidth, - boxHeight, - itemHeight: Math.max(fontSize, boxHeight) - }; + let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts; + + if (labelOpts.usePointStyle) { + boxHeight = Math.min(boxHeight, fontSize); + boxWidth = Math.min(boxWidth, fontSize); + } + + return { + boxWidth, + boxHeight, + itemHeight: Math.max(fontSize, boxHeight) + }; }; export class Legend extends Element { - /** + /** * @param {{ ctx: any; options: any; chart: any; }} config */ - constructor(config) { - super(); + constructor(config) { + super(); - this._added = false; + this._added = false; - // Contains hit boxes for each dataset (in dataset order) - this.legendHitBoxes = []; + // 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; - - this.chart = config.chart; - this.options = config.options; - this.ctx = config.ctx; - this.legendItems = undefined; - this.columnSizes = undefined; - this.lineWidths = undefined; - this.maxHeight = undefined; - this.maxWidth = undefined; - this.top = undefined; - this.bottom = undefined; - this.left = undefined; - this.right = undefined; - this.height = undefined; - this.width = undefined; - this._margins = undefined; - this.position = undefined; - this.weight = undefined; - this.fullSize = undefined; - } - - update(maxWidth, maxHeight, margins) { - const me = this; - - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me._margins = margins; - - me.setDimensions(); - me.buildLabels(); - me.fit(); - } - - setDimensions() { - const me = this; - - if (me.isHorizontal()) { - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - me.top = 0; - me.bottom = me.height; - } - } - - buildLabels() { - const me = this; - const labelOpts = me.options.labels || {}; - let legendItems = call(labelOpts.generateLabels, [me.chart], me) || []; - - if (labelOpts.filter) { - legendItems = legendItems.filter((item) => labelOpts.filter(item, me.chart.data)); - } - - if (labelOpts.sort) { - legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, me.chart.data)); - } - - if (me.options.reverse) { - legendItems.reverse(); - } - - me.legendItems = legendItems; - } - - fit() { - const me = this; - const {options, ctx} = me; - - // The legend may not be displayed for a variety of reasons including - // the fact that the defaults got set to `false`. - // When the legend is not displayed, there are no guarantees that the options - // are correctly formatted so we need to bail out as early as possible. - if (!options.display) { - me.width = me.height = 0; - return; - } - - const labelOpts = options.labels; - const labelFont = toFont(labelOpts.font, me.chart.options.font); - const fontSize = labelFont.size; - const titleHeight = me._computeTitleHeight(); - const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize); - - let width, height; - - ctx.font = labelFont.string; - - if (me.isHorizontal()) { - width = me.maxWidth; // fill all the width - height = me._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10; - } else { - height = me.maxHeight; // fill all the height - width = me._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10; - } - - me.width = Math.min(width, options.maxWidth || me.maxWidth); - me.height = Math.min(height, options.maxHeight || me.maxHeight); - } - - /** + this._hoveredItem = null; + + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + + this.chart = config.chart; + this.options = config.options; + this.ctx = config.ctx; + this.legendItems = undefined; + this.columnSizes = undefined; + this.lineWidths = undefined; + this.maxHeight = undefined; + this.maxWidth = undefined; + this.top = undefined; + this.bottom = undefined; + this.left = undefined; + this.right = undefined; + this.height = undefined; + this.width = undefined; + this._margins = undefined; + this.position = undefined; + this.weight = undefined; + this.fullSize = undefined; + } + + update(maxWidth, maxHeight, margins) { + const me = this; + + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me._margins = margins; + + me.setDimensions(); + me.buildLabels(); + me.fit(); + } + + setDimensions() { + const me = this; + + if (me.isHorizontal()) { + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + me.top = 0; + me.bottom = me.height; + } + } + + buildLabels() { + const me = this; + const labelOpts = me.options.labels || {}; + let legendItems = call(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter((item) => labelOpts.filter(item, me.chart.data)); + } + + if (labelOpts.sort) { + legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, me.chart.data)); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + } + + fit() { + const me = this; + const {options, ctx} = me; + + // The legend may not be displayed for a variety of reasons including + // the fact that the defaults got set to `false`. + // When the legend is not displayed, there are no guarantees that the options + // are correctly formatted so we need to bail out as early as possible. + if (!options.display) { + me.width = me.height = 0; + return; + } + + const labelOpts = options.labels; + const labelFont = toFont(labelOpts.font, me.chart.options.font); + const fontSize = labelFont.size; + const titleHeight = me._computeTitleHeight(); + const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize); + + let width, height; + + ctx.font = labelFont.string; + + if (me.isHorizontal()) { + width = me.maxWidth; // fill all the width + height = me._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10; + } else { + height = me.maxHeight; // fill all the height + width = me._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10; + } + + me.width = Math.min(width, options.maxWidth || me.maxWidth); + me.height = Math.min(height, options.maxHeight || me.maxHeight); + } + + /** * @private */ - _fitRows(titleHeight, fontSize, boxWidth, itemHeight) { - const me = this; - const {ctx, maxWidth} = me; - const padding = me.options.labels.padding; - const hitboxes = me.legendHitBoxes = []; - // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one - const lineWidths = me.lineWidths = [0]; - let totalHeight = titleHeight; - - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - - me.legendItems.forEach((legendItem, i) => { - const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) { - totalHeight += itemHeight + padding; - lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; - } - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = {left: 0, top: 0, width: itemWidth, height: itemHeight}; - - lineWidths[lineWidths.length - 1] += itemWidth + padding; - - }); - return totalHeight; - } - - _fitCols(titleHeight, fontSize, boxWidth, itemHeight) { - const me = this; - const {ctx, maxHeight} = me; - const padding = me.options.labels.padding; - const hitboxes = me.legendHitBoxes = []; - const columnSizes = me.columnSizes = []; - let totalWidth = padding; - let currentColWidth = 0; - let currentColHeight = 0; - - const heightLimit = maxHeight - titleHeight; - me.legendItems.forEach((legendItem, i) => { - const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - // If too tall, go to new column - if (i > 0 && currentColHeight + fontSize + 2 * padding > heightLimit) { - totalWidth += currentColWidth + padding; - columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size - currentColWidth = currentColHeight = 0; - } - - // Get max width - currentColWidth = Math.max(currentColWidth, itemWidth); - currentColHeight += fontSize + padding; - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = {left: 0, top: 0, width: itemWidth, height: itemHeight}; - }); - - totalWidth += currentColWidth; - columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size - - return totalWidth; - } - - isHorizontal() { - return this.options.position === 'top' || this.options.position === 'bottom'; - } - - draw() { - const me = this; - if (me.options.display) { - const ctx = me.ctx; - clipArea(ctx, me); - - me._draw(); - - unclipArea(ctx); - } - } - - /** + _fitRows(titleHeight, fontSize, boxWidth, itemHeight) { + const me = this; + const {ctx, maxWidth} = me; + const padding = me.options.labels.padding; + const hitboxes = me.legendHitBoxes = []; + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + const lineWidths = me.lineWidths = [0]; + let totalHeight = titleHeight; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + + me.legendItems.forEach((legendItem, i) => { + const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) { + totalHeight += itemHeight + padding; + lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = {left: 0, top: 0, width: itemWidth, height: itemHeight}; + + lineWidths[lineWidths.length - 1] += itemWidth + padding; + + }); + return totalHeight; + } + + _fitCols(titleHeight, fontSize, boxWidth, itemHeight) { + const me = this; + const {ctx, maxHeight} = me; + const padding = me.options.labels.padding; + const hitboxes = me.legendHitBoxes = []; + const columnSizes = me.columnSizes = []; + let totalWidth = padding; + let currentColWidth = 0; + let currentColHeight = 0; + + const heightLimit = maxHeight - titleHeight; + me.legendItems.forEach((legendItem, i) => { + const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (i > 0 && currentColHeight + fontSize + 2 * padding > heightLimit) { + totalWidth += currentColWidth + padding; + columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size + currentColWidth = currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += fontSize + padding; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = {left: 0, top: 0, width: itemWidth, height: itemHeight}; + }); + + totalWidth += currentColWidth; + columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size + + return totalWidth; + } + + isHorizontal() { + return this.options.position === 'top' || this.options.position === 'bottom'; + } + + draw() { + const me = this; + if (me.options.display) { + const ctx = me.ctx; + clipArea(ctx, me); + + me._draw(); + + unclipArea(ctx); + } + } + + /** * @private */ - _draw() { - const me = this; - const {options: opts, columnSizes, lineWidths, ctx, legendHitBoxes} = me; - const {align, labels: labelOpts} = opts; - const defaultColor = defaults.color; - const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width); - const labelFont = toFont(labelOpts.font, me.chart.options.font); - const {color: fontColor, padding} = labelOpts; - const fontSize = labelFont.size; - let cursor; - - me.drawTitle(); - - // Canvas setup - ctx.textAlign = rtlHelper.textAlign('left'); - ctx.textBaseline = 'middle'; - ctx.lineWidth = 0.5; - ctx.strokeStyle = fontColor; // for strikethrough effect - ctx.fillStyle = fontColor; // render in correct colour - ctx.font = labelFont.string; - - const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize); - - // current position - const drawLegendBox = function(x, y, legendItem) { - if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) { - return; - } - - // Set the ctx for the box - ctx.save(); - - const lineWidth = valueOrDefault(legendItem.lineWidth, 1); - ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); - ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt'); - ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0); - ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter'); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); - - ctx.setLineDash(valueOrDefault(legendItem.lineDash, [])); - - if (labelOpts.usePointStyle) { - // Recalculate x and y for drawPoint() because its expecting - // x and y to be center of figure (instead of top left) - const drawOptions = { - radius: boxWidth * Math.SQRT2 / 2, - pointStyle: legendItem.pointStyle, - rotation: legendItem.rotation, - borderWidth: lineWidth - }; - const centerX = rtlHelper.xPlus(x, boxWidth / 2); - const centerY = y + fontSize / 2; - - // Draw pointStyle as legend symbol - drawPoint(ctx, drawOptions, centerX, centerY); - } else { - // Draw box as legend symbol - // Adjust position when boxHeight < fontSize (want it centered) - const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0); - - ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), yBoxTop, boxWidth, boxHeight); - if (lineWidth !== 0) { - ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), yBoxTop, boxWidth, boxHeight); - } - } - - ctx.restore(); - }; - - const fillText = function(x, y, legendItem) { - const halfFontSize = fontSize / 2; - const xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); - renderText(ctx, legendItem.text, xLeft, y + (itemHeight / 2), labelFont, {strikethrough: legendItem.hidden}); - }; - - // Horizontal - const isHorizontal = me.isHorizontal(); - const titleHeight = this._computeTitleHeight(); - if (isHorizontal) { - cursor = { - x: _alignStartEnd(align, me.left + padding, me.right - lineWidths[0]), - y: me.top + padding + titleHeight, - line: 0 - }; - } else { - cursor = { - x: me.left + padding, - y: _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[0].height), - line: 0 - }; - } - - overrideTextDirection(me.ctx, opts.textDirection); - - const lineHeight = itemHeight + padding; - me.legendItems.forEach((legendItem, i) => { - const textWidth = ctx.measureText(legendItem.text).width; - const width = boxWidth + (fontSize / 2) + textWidth; - let x = cursor.x; - let y = cursor.y; - - rtlHelper.setWidth(me.width); - - if (isHorizontal) { - if (i > 0 && x + width + padding > me.right) { - y = cursor.y += lineHeight; - cursor.line++; - x = cursor.x = _alignStartEnd(align, me.left + padding, me.right - lineWidths[cursor.line]); - } - } else if (i > 0 && y + lineHeight > me.bottom) { - x = cursor.x = x + columnSizes[cursor.line].width + padding; - cursor.line++; - y = cursor.y = _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[cursor.line].height); - } - - const realX = rtlHelper.x(x); - - drawLegendBox(realX, y, legendItem); - - legendHitBoxes[i].left = rtlHelper.leftForLtr(realX, legendHitBoxes[i].width); - legendHitBoxes[i].top = y; - - // Fill the actual label - fillText(realX, y, legendItem); - - if (isHorizontal) { - cursor.x += width + padding; - } else { - cursor.y += lineHeight; - } - }); - - restoreTextDirection(me.ctx, opts.textDirection); - } - - /** + _draw() { + const me = this; + const {options: opts, columnSizes, lineWidths, ctx, legendHitBoxes} = me; + const {align, labels: labelOpts} = opts; + const defaultColor = defaults.color; + const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width); + const labelFont = toFont(labelOpts.font, me.chart.options.font); + const {color: fontColor, padding} = labelOpts; + const fontSize = labelFont.size; + let cursor; + + me.drawTitle(); + + // Canvas setup + ctx.textAlign = rtlHelper.textAlign('left'); + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont.string; + + const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize); + + // current position + const drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + const lineWidth = valueOrDefault(legendItem.lineWidth, 1); + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); + ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt'); + ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0); + ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter'); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); + + ctx.setLineDash(valueOrDefault(legendItem.lineDash, [])); + + if (labelOpts.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + const drawOptions = { + radius: boxWidth * Math.SQRT2 / 2, + pointStyle: legendItem.pointStyle, + rotation: legendItem.rotation, + borderWidth: lineWidth + }; + const centerX = rtlHelper.xPlus(x, boxWidth / 2); + const centerY = y + fontSize / 2; + + // Draw pointStyle as legend symbol + drawPoint(ctx, drawOptions, centerX, centerY); + } else { + // Draw box as legend symbol + // Adjust position when boxHeight < fontSize (want it centered) + const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0); + + ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), yBoxTop, boxWidth, boxHeight); + if (lineWidth !== 0) { + ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), yBoxTop, boxWidth, boxHeight); + } + } + + ctx.restore(); + }; + + const fillText = function(x, y, legendItem) { + const halfFontSize = fontSize / 2; + const xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); + renderText(ctx, legendItem.text, xLeft, y + (itemHeight / 2), labelFont, {strikethrough: legendItem.hidden}); + }; + + // Horizontal + const isHorizontal = me.isHorizontal(); + const titleHeight = this._computeTitleHeight(); + if (isHorizontal) { + cursor = { + x: _alignStartEnd(align, me.left + padding, me.right - lineWidths[0]), + y: me.top + padding + titleHeight, + line: 0 + }; + } else { + cursor = { + x: me.left + padding, + y: _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[0].height), + line: 0 + }; + } + + overrideTextDirection(me.ctx, opts.textDirection); + + const lineHeight = itemHeight + padding; + me.legendItems.forEach((legendItem, i) => { + const textWidth = ctx.measureText(legendItem.text).width; + const width = boxWidth + (fontSize / 2) + textWidth; + let x = cursor.x; + let y = cursor.y; + + rtlHelper.setWidth(me.width); + + if (isHorizontal) { + if (i > 0 && x + width + padding > me.right) { + y = cursor.y += lineHeight; + cursor.line++; + x = cursor.x = _alignStartEnd(align, me.left + padding, me.right - lineWidths[cursor.line]); + } + } else if (i > 0 && y + lineHeight > me.bottom) { + x = cursor.x = x + columnSizes[cursor.line].width + padding; + cursor.line++; + y = cursor.y = _alignStartEnd(align, me.top + titleHeight + padding, me.bottom - columnSizes[cursor.line].height); + } + + const realX = rtlHelper.x(x); + + drawLegendBox(realX, y, legendItem); + + legendHitBoxes[i].left = rtlHelper.leftForLtr(realX, legendHitBoxes[i].width); + legendHitBoxes[i].top = y; + + // Fill the actual label + fillText(realX, y, legendItem); + + if (isHorizontal) { + cursor.x += width + padding; + } else { + cursor.y += lineHeight; + } + }); + + restoreTextDirection(me.ctx, opts.textDirection); + } + + /** * @protected */ - drawTitle() { - const me = this; - const opts = me.options; - const titleOpts = opts.title; - const titleFont = toFont(titleOpts.font, me.chart.options.font); - const titlePadding = toPadding(titleOpts.padding); - - if (!titleOpts.display) { - return; - } - - const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width); - const ctx = me.ctx; - const position = titleOpts.position; - const halfFontSize = titleFont.size / 2; - const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize; - let y; - - // These defaults are used when the legend is vertical. - // When horizontal, they are computed below. - let left = me.left; - let maxWidth = me.width; - - if (this.isHorizontal()) { - // Move left / right so that the title is above the legend lines - maxWidth = Math.max(...me.lineWidths); - y = me.top + topPaddingPlusHalfFontSize; - left = _alignStartEnd(opts.align, left, me.right - maxWidth); - } else { - // Move down so that the title is above the legend stack in every alignment - const maxHeight = me.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0); - y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, me.top, me.bottom - maxHeight - opts.labels.padding - me._computeTitleHeight()); - } - - // Now that we know the left edge of the inner legend box, compute the correct - // X coordinate from the title alignment - const x = _alignStartEnd(position, left, left + maxWidth); - - // Canvas setup - ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position)); - ctx.textBaseline = 'middle'; - ctx.strokeStyle = titleOpts.color; - ctx.fillStyle = titleOpts.color; - ctx.font = titleFont.string; - - renderText(ctx, titleOpts.text, x, y, titleFont); - } - - /** + drawTitle() { + const me = this; + const opts = me.options; + const titleOpts = opts.title; + const titleFont = toFont(titleOpts.font, me.chart.options.font); + const titlePadding = toPadding(titleOpts.padding); + + if (!titleOpts.display) { + return; + } + + const rtlHelper = getRtlAdapter(opts.rtl, me.left, me.width); + const ctx = me.ctx; + const position = titleOpts.position; + const halfFontSize = titleFont.size / 2; + const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize; + let y; + + // These defaults are used when the legend is vertical. + // When horizontal, they are computed below. + let left = me.left; + let maxWidth = me.width; + + if (this.isHorizontal()) { + // Move left / right so that the title is above the legend lines + maxWidth = Math.max(...me.lineWidths); + y = me.top + topPaddingPlusHalfFontSize; + left = _alignStartEnd(opts.align, left, me.right - maxWidth); + } else { + // Move down so that the title is above the legend stack in every alignment + const maxHeight = me.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0); + y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, me.top, me.bottom - maxHeight - opts.labels.padding - me._computeTitleHeight()); + } + + // Now that we know the left edge of the inner legend box, compute the correct + // X coordinate from the title alignment + const x = _alignStartEnd(position, left, left + maxWidth); + + // Canvas setup + ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position)); + ctx.textBaseline = 'middle'; + ctx.strokeStyle = titleOpts.color; + ctx.fillStyle = titleOpts.color; + ctx.font = titleFont.string; + + renderText(ctx, titleOpts.text, x, y, titleFont); + } + + /** * @private */ - _computeTitleHeight() { - const titleOpts = this.options.title; - const titleFont = toFont(titleOpts.font, this.chart.options.font); - const titlePadding = toPadding(titleOpts.padding); - return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0; - } - - /** + _computeTitleHeight() { + const titleOpts = this.options.title; + const titleFont = toFont(titleOpts.font, this.chart.options.font); + const titlePadding = toPadding(titleOpts.padding); + return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0; + } + + /** * @private */ - _getLegendItemAt(x, y) { - const me = this; - let 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; - } - - /** + _getLegendItemAt(x, y) { + const me = this; + let 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 * @param {ChartEvent} e - The event to handle */ - handleEvent(e) { - const me = this; - const opts = me.options; - if (!isListened(e.type, opts)) { - return; - } - - // Chart event already has relative position in it - const hoveredItem = me._getLegendItemAt(e.x, e.y); - - if (e.type === 'mousemove') { - const previous = me._hoveredItem; - if (previous && previous !== hoveredItem) { - call(opts.onLeave, [e, previous, me], me); - } - - me._hoveredItem = hoveredItem; - - if (hoveredItem) { - call(opts.onHover, [e, hoveredItem, me], me); - } - } else if (hoveredItem) { - call(opts.onClick, [e, hoveredItem, me], me); - } - } + handleEvent(e) { + const me = this; + const opts = me.options; + if (!isListened(e.type, opts)) { + return; + } + + // Chart event already has relative position in it + const hoveredItem = me._getLegendItemAt(e.x, e.y); + + if (e.type === 'mousemove') { + const previous = me._hoveredItem; + if (previous && previous !== hoveredItem) { + call(opts.onLeave, [e, previous, me], me); + } + + me._hoveredItem = hoveredItem; + + if (hoveredItem) { + call(opts.onHover, [e, hoveredItem, me], me); + } + } else if (hoveredItem) { + call(opts.onClick, [e, hoveredItem, me], me); + } + } } function isListened(type, opts) { - if (type === 'mousemove' && (opts.onHover || opts.onLeave)) { - return true; - } - if (opts.onClick && (type === 'click' || type === 'mouseup')) { - return true; - } - return false; + if (type === 'mousemove' && (opts.onHover || opts.onLeave)) { + return true; + } + if (opts.onClick && (type === 'click' || type === 'mouseup')) { + return true; + } + return false; } export default { - id: 'legend', + id: 'legend', - /** + /** * For tests * @private */ - _element: Legend, - - start(chart, _args, options) { - const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart}); - layouts.configure(chart, legend, options); - layouts.addBox(chart, legend); - }, - - stop(chart) { - layouts.removeBox(chart, chart.legend); - delete chart.legend; - }, - - // During the beforeUpdate step, the layout configuration needs to run - // This ensures that if the legend position changes (via an option update) - // the layout system respects the change. See https://github.com/chartjs/Chart.js/issues/7527 - beforeUpdate(chart, _args, options) { - const legend = chart.legend; - layouts.configure(chart, legend, options); - legend.options = options; - }, - - // The labels need to be built after datasets are updated to ensure that colors - // and other styling are correct. See https://github.com/chartjs/Chart.js/issues/6968 - afterUpdate(chart) { - chart.legend.buildLabels(); - }, - - - afterEvent(chart, args) { - chart.legend.handleEvent(args.event); - }, - - defaults: { - display: true, - position: 'top', - align: 'center', - fullSize: true, - reverse: false, - weight: 1000, - - // a callback that will handle - onClick(e, legendItem, legend) { - const index = legendItem.datasetIndex; - const ci = legend.chart; - if (ci.isDatasetVisible(index)) { - ci.hide(index); - legendItem.hidden = true; - } else { - ci.show(index); - legendItem.hidden = false; - } - }, - - onHover: null, - onLeave: null, - - labels: { - boxWidth: 40, - padding: 10, - // Generates labels shown in the legend - // Valid properties to return: - // text : text to display - // fillStyle : fill of coloured box - // strokeStyle: stroke of coloured box - // hidden : if this legend item refers to a hidden item - // lineCap : cap style for line - // lineDash - // lineDashOffset : - // lineJoin : - // lineWidth : - generateLabels(chart) { - const datasets = chart.data.datasets; - const {labels} = chart.legend.options; - const usePointStyle = labels.usePointStyle; - const overrideStyle = labels.pointStyle; - - return chart._getSortedDatasetMetas().map((meta) => { - const style = meta.controller.getStyle(usePointStyle ? 0 : undefined); - const borderWidth = isObject(style.borderWidth) ? (valueOrDefault(style.borderWidth.top, 0) + valueOrDefault(style.borderWidth.left, 0) + valueOrDefault(style.borderWidth.bottom, 0) + valueOrDefault(style.borderWidth.right, 0)) / 4 : style.borderWidth; - - return { - text: datasets[meta.index].label, - fillStyle: style.backgroundColor, - hidden: !meta.visible, - lineCap: style.borderCapStyle, - lineDash: style.borderDash, - lineDashOffset: style.borderDashOffset, - lineJoin: style.borderJoinStyle, - lineWidth: borderWidth, - strokeStyle: style.borderColor, - pointStyle: overrideStyle || style.pointStyle, - rotation: style.rotation, - - // Below is extra data used for toggling the datasets - datasetIndex: meta.index - }; - }, this); - } - }, - - title: { - display: false, - position: 'center', - text: '', - } - }, - - defaultRoutes: { - 'labels.color': 'color', - 'title.color': 'color' - } + _element: Legend, + + start(chart, _args, options) { + const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart}); + layouts.configure(chart, legend, options); + layouts.addBox(chart, legend); + }, + + stop(chart) { + layouts.removeBox(chart, chart.legend); + delete chart.legend; + }, + + // During the beforeUpdate step, the layout configuration needs to run + // This ensures that if the legend position changes (via an option update) + // the layout system respects the change. See https://github.com/chartjs/Chart.js/issues/7527 + beforeUpdate(chart, _args, options) { + const legend = chart.legend; + layouts.configure(chart, legend, options); + legend.options = options; + }, + + // The labels need to be built after datasets are updated to ensure that colors + // and other styling are correct. See https://github.com/chartjs/Chart.js/issues/6968 + afterUpdate(chart) { + chart.legend.buildLabels(); + }, + + + afterEvent(chart, args) { + chart.legend.handleEvent(args.event); + }, + + defaults: { + display: true, + position: 'top', + align: 'center', + fullSize: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick(e, legendItem, legend) { + const index = legendItem.datasetIndex; + const ci = legend.chart; + if (ci.isDatasetVisible(index)) { + ci.hide(index); + legendItem.hidden = true; + } else { + ci.show(index); + legendItem.hidden = false; + } + }, + + onHover: null, + onLeave: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels(chart) { + const datasets = chart.data.datasets; + const {labels} = chart.legend.options; + const usePointStyle = labels.usePointStyle; + const overrideStyle = labels.pointStyle; + + return chart._getSortedDatasetMetas().map((meta) => { + const style = meta.controller.getStyle(usePointStyle ? 0 : undefined); + const borderWidth = isObject(style.borderWidth) ? (valueOrDefault(style.borderWidth.top, 0) + valueOrDefault(style.borderWidth.left, 0) + valueOrDefault(style.borderWidth.bottom, 0) + valueOrDefault(style.borderWidth.right, 0)) / 4 : style.borderWidth; + + return { + text: datasets[meta.index].label, + fillStyle: style.backgroundColor, + hidden: !meta.visible, + lineCap: style.borderCapStyle, + lineDash: style.borderDash, + lineDashOffset: style.borderDashOffset, + lineJoin: style.borderJoinStyle, + lineWidth: borderWidth, + strokeStyle: style.borderColor, + pointStyle: overrideStyle || style.pointStyle, + rotation: style.rotation, + + // Below is extra data used for toggling the datasets + datasetIndex: meta.index + }; + }, this); + } + }, + + title: { + display: false, + position: 'center', + text: '', + } + }, + + defaultRoutes: { + 'labels.color': 'color', + 'title.color': 'color' + } }; diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 415afc76053..6543f5b29cc 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -5,179 +5,179 @@ import {_toLeftRightCenter, _alignStartEnd} from '../helpers/helpers.extras'; import {renderText} from '../helpers/helpers.canvas'; export class Title extends Element { - /** + /** * @param {{ ctx: any; options: any; chart: any; }} config */ - constructor(config) { - super(); - - this.chart = config.chart; - this.options = config.options; - this.ctx = config.ctx; - this._padding = undefined; - this.top = undefined; - this.bottom = undefined; - this.left = undefined; - this.right = undefined; - this.width = undefined; - this.height = undefined; - this.position = undefined; - this.weight = undefined; - this.fullSize = undefined; - } - - update(maxWidth, maxHeight) { - const me = this; - const opts = me.options; - - me.left = 0; - me.top = 0; - - if (!opts.display) { - me.width = me.height = me.right = me.bottom = 0; - return; - } - - me.width = me.right = maxWidth; - me.height = me.bottom = maxHeight; - - const lineCount = isArray(opts.text) ? opts.text.length : 1; - me._padding = toPadding(opts.padding); - const textSize = lineCount * toFont(opts.font, me.chart.options.font).lineHeight + me._padding.height; - - if (me.isHorizontal()) { - me.height = textSize; - } else { - me.width = textSize; - } - } - - isHorizontal() { - const pos = this.options.position; - return pos === 'top' || pos === 'bottom'; - } - - _drawArgs(offset) { - const {top, left, bottom, right, options} = this; - const align = options.align; - let rotation = 0; - let maxWidth, titleX, titleY; - - if (this.isHorizontal()) { - titleX = _alignStartEnd(align, left, right); - titleY = top + offset; - maxWidth = right - left; - } else { - if (options.position === 'left') { - titleX = left + offset; - titleY = _alignStartEnd(align, bottom, top); - rotation = PI * -0.5; - } else { - titleX = right - offset; - titleY = _alignStartEnd(align, top, bottom); - rotation = PI * 0.5; - } - maxWidth = bottom - top; - } - return {titleX, titleY, maxWidth, rotation}; - } - - draw() { - const me = this; - const ctx = me.ctx; - const opts = me.options; - - if (!opts.display) { - return; - } - - const fontOpts = toFont(opts.font, me.chart.options.font); - const lineHeight = fontOpts.lineHeight; - const offset = lineHeight / 2 + me._padding.top; - const {titleX, titleY, maxWidth, rotation} = me._drawArgs(offset); - - renderText(ctx, opts.text, 0, 0, fontOpts, { - color: opts.color, - maxWidth, - rotation, - textAlign: _toLeftRightCenter(opts.align), - textBaseline: 'middle', - translation: [titleX, titleY], - }); - } + constructor(config) { + super(); + + this.chart = config.chart; + this.options = config.options; + this.ctx = config.ctx; + this._padding = undefined; + this.top = undefined; + this.bottom = undefined; + this.left = undefined; + this.right = undefined; + this.width = undefined; + this.height = undefined; + this.position = undefined; + this.weight = undefined; + this.fullSize = undefined; + } + + update(maxWidth, maxHeight) { + const me = this; + const opts = me.options; + + me.left = 0; + me.top = 0; + + if (!opts.display) { + me.width = me.height = me.right = me.bottom = 0; + return; + } + + me.width = me.right = maxWidth; + me.height = me.bottom = maxHeight; + + const lineCount = isArray(opts.text) ? opts.text.length : 1; + me._padding = toPadding(opts.padding); + const textSize = lineCount * toFont(opts.font, me.chart.options.font).lineHeight + me._padding.height; + + if (me.isHorizontal()) { + me.height = textSize; + } else { + me.width = textSize; + } + } + + isHorizontal() { + const pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + } + + _drawArgs(offset) { + const {top, left, bottom, right, options} = this; + const align = options.align; + let rotation = 0; + let maxWidth, titleX, titleY; + + if (this.isHorizontal()) { + titleX = _alignStartEnd(align, left, right); + titleY = top + offset; + maxWidth = right - left; + } else { + if (options.position === 'left') { + titleX = left + offset; + titleY = _alignStartEnd(align, bottom, top); + rotation = PI * -0.5; + } else { + titleX = right - offset; + titleY = _alignStartEnd(align, top, bottom); + rotation = PI * 0.5; + } + maxWidth = bottom - top; + } + return {titleX, titleY, maxWidth, rotation}; + } + + draw() { + const me = this; + const ctx = me.ctx; + const opts = me.options; + + if (!opts.display) { + return; + } + + const fontOpts = toFont(opts.font, me.chart.options.font); + const lineHeight = fontOpts.lineHeight; + const offset = lineHeight / 2 + me._padding.top; + const {titleX, titleY, maxWidth, rotation} = me._drawArgs(offset); + + renderText(ctx, opts.text, 0, 0, fontOpts, { + color: opts.color, + maxWidth, + rotation, + textAlign: _toLeftRightCenter(opts.align), + textBaseline: 'middle', + translation: [titleX, titleY], + }); + } } function createTitle(chart, titleOpts) { - const title = new Title({ - ctx: chart.ctx, - options: titleOpts, - chart - }); - - layouts.configure(chart, title, titleOpts); - layouts.addBox(chart, title); - chart.titleBlock = title; + const title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart + }); + + layouts.configure(chart, title, titleOpts); + layouts.addBox(chart, title); + chart.titleBlock = title; } function removeTitle(chart) { - const title = chart.titleBlock; - if (title) { - layouts.removeBox(chart, title); - delete chart.titleBlock; - } + const title = chart.titleBlock; + if (title) { + layouts.removeBox(chart, title); + delete chart.titleBlock; + } } function createOrUpdateTitle(chart, options) { - const title = chart.titleBlock; - if (title) { - layouts.configure(chart, title, options); - title.options = options; - } else { - createTitle(chart, options); - } + const title = chart.titleBlock; + if (title) { + layouts.configure(chart, title, options); + title.options = options; + } else { + createTitle(chart, options); + } } export default { - id: 'title', + id: 'title', - /** + /** * For tests * @private */ - _element: Title, - - start(chart, _args, options) { - createTitle(chart, options); - }, - - stop(chart) { - const titleBlock = chart.titleBlock; - layouts.removeBox(chart, titleBlock); - delete chart.titleBlock; - }, - - beforeUpdate(chart, _args, options) { - if (options === false) { - removeTitle(chart); - } else { - createOrUpdateTitle(chart, options); - } - }, - - defaults: { - align: 'center', - display: false, - font: { - style: 'bold', - }, - fullSize: true, - padding: 10, - position: 'top', - text: '', - weight: 2000 // by default greater than legend (1000) to be above - }, - - defaultRoutes: { - color: 'color' - } + _element: Title, + + start(chart, _args, options) { + createTitle(chart, options); + }, + + stop(chart) { + const titleBlock = chart.titleBlock; + layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + }, + + beforeUpdate(chart, _args, options) { + if (options === false) { + removeTitle(chart); + } else { + createOrUpdateTitle(chart, options); + } + }, + + defaults: { + align: 'center', + display: false, + font: { + style: 'bold', + }, + fullSize: true, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + }, + + defaultRoutes: { + color: 'color' + } }; diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index cb230dd5c5e..566481554f4 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -10,89 +10,89 @@ import {drawPoint, toFontString} from '../helpers'; */ const positioners = { - /** + /** * Average mode places the tooltip at the average position of the elements shown * @function Chart.Tooltip.positioners.average * @param items {object[]} the items being displayed in the tooltip * @returns {object} tooltip position */ - average(items) { - if (!items.length) { - return false; - } - - let i, len; - let x = 0; - let y = 0; - let count = 0; - - for (i = 0, len = items.length; i < len; ++i) { - const el = items[i].element; - if (el && el.hasValue()) { - const pos = el.tooltipPosition(); - x += pos.x; - y += pos.y; - ++count; - } - } - - return { - x: x / count, - y: y / count - }; - }, - - /** + average(items) { + if (!items.length) { + return false; + } + + let i, len; + let x = 0; + let y = 0; + let count = 0; + + for (i = 0, len = items.length; i < len; ++i) { + const el = items[i].element; + if (el && el.hasValue()) { + const pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: x / count, + y: y / count + }; + }, + + /** * Gets the tooltip position nearest of the item nearest to the event position * @function Chart.Tooltip.positioners.nearest * @param items {object[]} the tooltip items * @param eventPosition {object} the position of the event in canvas coordinates * @returns {object} the tooltip position */ - nearest(items, eventPosition) { - let x = eventPosition.x; - let y = eventPosition.y; - let minDistance = Number.POSITIVE_INFINITY; - let i, len, nearestElement; - - for (i = 0, len = items.length; i < len; ++i) { - const el = items[i].element; - if (el && el.hasValue()) { - const center = el.getCenterPoint(); - const d = distanceBetweenPoints(eventPosition, center); - - if (d < minDistance) { - minDistance = d; - nearestElement = el; - } - } - } - - if (nearestElement) { - const tp = nearestElement.tooltipPosition(); - x = tp.x; - y = tp.y; - } - - return { - x, - y - }; - } + nearest(items, eventPosition) { + let x = eventPosition.x; + let y = eventPosition.y; + let minDistance = Number.POSITIVE_INFINITY; + let i, len, nearestElement; + + for (i = 0, len = items.length; i < len; ++i) { + const el = items[i].element; + if (el && el.hasValue()) { + const center = el.getCenterPoint(); + const d = distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + const tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x, + y + }; + } }; // Helper to push or concat based on if the 2nd parameter is an array or not function pushOrConcat(base, toPush) { - if (toPush) { - if (isArray(toPush)) { - // base = base.concat(toPush); - Array.prototype.push.apply(base, toPush); - } else { - base.push(toPush); - } - } - - return base; + if (toPush) { + if (isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; } /** @@ -102,10 +102,10 @@ function pushOrConcat(base, toPush) { * @function */ function splitNewlines(str) { - if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { - return str.split('\n'); - } - return str; + if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { + return str.split('\n'); + } + return str; } @@ -115,905 +115,905 @@ function splitNewlines(str) { * @return new tooltip item */ function createTooltipItem(chart, item) { - const {element, datasetIndex, index} = item; - const controller = chart.getDatasetMeta(datasetIndex).controller; - const {label, value} = controller.getLabelAndValue(index); - - return { - chart, - label, - parsed: controller.getParsed(index), - raw: chart.data.datasets[datasetIndex].data[index], - formattedValue: value, - dataset: controller.getDataset(), - dataIndex: index, - datasetIndex, - element - }; + const {element, datasetIndex, index} = item; + const controller = chart.getDatasetMeta(datasetIndex).controller; + const {label, value} = controller.getLabelAndValue(index); + + return { + chart, + label, + parsed: controller.getParsed(index), + raw: chart.data.datasets[datasetIndex].data[index], + formattedValue: value, + dataset: controller.getDataset(), + dataIndex: index, + datasetIndex, + element + }; } /** * Get the size of the tooltip */ function getTooltipSize(tooltip) { - const ctx = tooltip._chart.ctx; - const {body, footer, options, title} = tooltip; - const {bodyFont, footerFont, titleFont, boxWidth, boxHeight} = options; - const titleLineCount = title.length; - const footerLineCount = footer.length; - const bodyLineItemCount = body.length; - - let height = options.yPadding * 2; // Tooltip Padding - let width = 0; - - // Count of all lines in the body - let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0); - combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length; - - if (titleLineCount) { - height += titleLineCount * titleFont.size + const ctx = tooltip._chart.ctx; + const {body, footer, options, title} = tooltip; + const {bodyFont, footerFont, titleFont, boxWidth, boxHeight} = options; + const titleLineCount = title.length; + const footerLineCount = footer.length; + const bodyLineItemCount = body.length; + + let height = options.yPadding * 2; // Tooltip Padding + let width = 0; + + // Count of all lines in the body + let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0); + combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length; + + if (titleLineCount) { + height += titleLineCount * titleFont.size + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom; - } - if (combinedBodyLength) { - // Body lines may include some extra height depending on boxHeight - const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.size) : bodyFont.size; - height += bodyLineItemCount * bodyLineHeight + } + if (combinedBodyLength) { + // Body lines may include some extra height depending on boxHeight + const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.size) : bodyFont.size; + height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.size + (combinedBodyLength - 1) * options.bodySpacing; - } - if (footerLineCount) { - height += options.footerMarginTop + } + if (footerLineCount) { + height += options.footerMarginTop + footerLineCount * footerFont.size + (footerLineCount - 1) * options.footerSpacing; - } + } - // Title width - let widthPadding = 0; - const maxLineWidth = function(line) { - width = Math.max(width, ctx.measureText(line).width + widthPadding); - }; + // Title width + let widthPadding = 0; + const maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; - ctx.save(); + ctx.save(); - ctx.font = toFontString(titleFont); - each(tooltip.title, maxLineWidth); + ctx.font = toFontString(titleFont); + each(tooltip.title, maxLineWidth); - // Body width - ctx.font = toFontString(bodyFont); - each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth); + // Body width + ctx.font = toFontString(bodyFont); + each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth); - // Body lines may include some extra width due to the color box - widthPadding = options.displayColors ? (boxWidth + 2) : 0; - each(body, (bodyItem) => { - each(bodyItem.before, maxLineWidth); - each(bodyItem.lines, maxLineWidth); - each(bodyItem.after, maxLineWidth); - }); + // Body lines may include some extra width due to the color box + widthPadding = options.displayColors ? (boxWidth + 2) : 0; + each(body, (bodyItem) => { + each(bodyItem.before, maxLineWidth); + each(bodyItem.lines, maxLineWidth); + each(bodyItem.after, maxLineWidth); + }); - // Reset back to 0 - widthPadding = 0; + // Reset back to 0 + widthPadding = 0; - // Footer width - ctx.font = toFontString(footerFont); - each(tooltip.footer, maxLineWidth); + // Footer width + ctx.font = toFontString(footerFont); + each(tooltip.footer, maxLineWidth); - ctx.restore(); + ctx.restore(); - // Add padding - width += 2 * options.xPadding; + // Add padding + width += 2 * options.xPadding; - return {width, height}; + return {width, height}; } /** * Helper to get the alignment of a tooltip given the size */ function determineAlignment(chart, options, size) { - const {x, y, width, height} = size; - const chartArea = chart.chartArea; - let xAlign = 'center'; - let yAlign = 'center'; - - if (y < height / 2) { - yAlign = 'top'; - } else if (y > (chart.height - height / 2)) { - yAlign = 'bottom'; - } - - let lf, rf; // functions to determine left, right alignment - const midX = (chartArea.left + chartArea.right) / 2; - const midY = (chartArea.top + chartArea.bottom) / 2; - - if (yAlign === 'center') { - lf = (value) => value <= midX; - rf = (value) => value > midX; - } else { - lf = (value) => value <= (width / 2); - rf = (value) => value >= (chart.width - (width / 2)); - } - - // functions to determine if left/right alignment causes tooltip to go outside chart - const olf = (value) => value + width + options.caretSize + options.caretPadding > chart.width; - const orf = (value) => value - width - options.caretSize - options.caretPadding < 0; - // function to get the y alignment if the tooltip goes outside of the left or right edges - const yf = (value) => value <= midY ? 'top' : 'bottom'; - - if (lf(x)) { - xAlign = 'left'; - - // Is tooltip too wide and goes over the right side of the chart.? - if (olf(x)) { - xAlign = 'center'; - yAlign = yf(y); - } - } else if (rf(x)) { - xAlign = 'right'; - - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(x)) { - xAlign = 'center'; - yAlign = yf(y); - } - } - - return { - xAlign: options.xAlign ? options.xAlign : xAlign, - yAlign: options.yAlign ? options.yAlign : yAlign - }; + const {x, y, width, height} = size; + const chartArea = chart.chartArea; + let xAlign = 'center'; + let yAlign = 'center'; + + if (y < height / 2) { + yAlign = 'top'; + } else if (y > (chart.height - height / 2)) { + yAlign = 'bottom'; + } + + let lf, rf; // functions to determine left, right alignment + const midX = (chartArea.left + chartArea.right) / 2; + const midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = (value) => value <= midX; + rf = (value) => value > midX; + } else { + lf = (value) => value <= (width / 2); + rf = (value) => value >= (chart.width - (width / 2)); + } + + // functions to determine if left/right alignment causes tooltip to go outside chart + const olf = (value) => value + width + options.caretSize + options.caretPadding > chart.width; + const orf = (value) => value - width - options.caretSize - options.caretPadding < 0; + // function to get the y alignment if the tooltip goes outside of the left or right edges + const yf = (value) => value <= midY ? 'top' : 'bottom'; + + if (lf(x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(x)) { + xAlign = 'center'; + yAlign = yf(y); + } + } else if (rf(x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(x)) { + xAlign = 'center'; + yAlign = yf(y); + } + } + + return { + xAlign: options.xAlign ? options.xAlign : xAlign, + yAlign: options.yAlign ? options.yAlign : yAlign + }; } function alignX(size, xAlign, chartWidth) { - // eslint-disable-next-line prefer-const - let {x, width} = size; - if (xAlign === 'right') { - x -= width; - } else if (xAlign === 'center') { - x -= (width / 2); - if (x + width > chartWidth) { - x = chartWidth - width; - } - if (x < 0) { - x = 0; - } - } - return x; + // eslint-disable-next-line prefer-const + let {x, width} = size; + if (xAlign === 'right') { + x -= width; + } else if (xAlign === 'center') { + x -= (width / 2); + if (x + width > chartWidth) { + x = chartWidth - width; + } + if (x < 0) { + x = 0; + } + } + return x; } function alignY(size, yAlign, paddingAndSize) { - // eslint-disable-next-line prefer-const - let {y, height} = size; - if (yAlign === 'top') { - y += paddingAndSize; - } else if (yAlign === 'bottom') { - y -= height + paddingAndSize; - } else { - y -= (height / 2); - } - return y; + // eslint-disable-next-line prefer-const + let {y, height} = size; + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= height + paddingAndSize; + } else { + y -= (height / 2); + } + return y; } /** * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment */ function getBackgroundPoint(options, size, alignment, chart) { - const {caretSize, caretPadding, cornerRadius} = options; - const {xAlign, yAlign} = alignment; - const paddingAndSize = caretSize + caretPadding; - const radiusAndPadding = cornerRadius + caretPadding; - - let x = alignX(size, xAlign, chart.width); - const y = alignY(size, yAlign, paddingAndSize); - - if (yAlign === 'center') { - if (xAlign === 'left') { - x += paddingAndSize; - } else if (xAlign === 'right') { - x -= paddingAndSize; - } - } else if (xAlign === 'left') { - x -= radiusAndPadding; - } else if (xAlign === 'right') { - x += radiusAndPadding; - } - - return {x, y}; + const {caretSize, caretPadding, cornerRadius} = options; + const {xAlign, yAlign} = alignment; + const paddingAndSize = caretSize + caretPadding; + const radiusAndPadding = cornerRadius + caretPadding; + + let x = alignX(size, xAlign, chart.width); + const y = alignY(size, yAlign, paddingAndSize); + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return {x, y}; } function getAlignedX(tooltip, align) { - const options = tooltip.options; - return align === 'center' - ? tooltip.x + tooltip.width / 2 - : align === 'right' - ? tooltip.x + tooltip.width - options.xPadding - : tooltip.x + options.xPadding; + const options = tooltip.options; + return align === 'center' + ? tooltip.x + tooltip.width / 2 + : align === 'right' + ? tooltip.x + tooltip.width - options.xPadding + : tooltip.x + options.xPadding; } /** * Helper to build before and after body lines */ function getBeforeAfterBodyLines(callback) { - return pushOrConcat([], splitNewlines(callback)); + return pushOrConcat([], splitNewlines(callback)); } export class Tooltip extends Element { - constructor(config) { - super(); - - this.opacity = 0; - this._active = []; - this._chart = config._chart; - this._eventPosition = undefined; - this._size = undefined; - this._cachedAnimations = undefined; - this.$animations = undefined; - this.options = config.options; - this.dataPoints = undefined; - this.title = undefined; - this.beforeBody = undefined; - this.body = undefined; - this.afterBody = undefined; - this.footer = undefined; - this.xAlign = undefined; - this.yAlign = undefined; - this.x = undefined; - this.y = undefined; - this.height = undefined; - this.width = undefined; - this.caretX = undefined; - this.caretY = undefined; - this.labelColors = undefined; - this.labelPointStyles = undefined; - this.labelTextColors = undefined; - } - - initialize(options) { - const defaultSize = options.bodyFont.size; - options.boxHeight = valueOrDefault(options.boxHeight, defaultSize); - options.boxWidth = valueOrDefault(options.boxWidth, defaultSize); - this.options = options; - this._cachedAnimations = undefined; - } - - /** + constructor(config) { + super(); + + this.opacity = 0; + this._active = []; + this._chart = config._chart; + this._eventPosition = undefined; + this._size = undefined; + this._cachedAnimations = undefined; + this.$animations = undefined; + this.options = config.options; + this.dataPoints = undefined; + this.title = undefined; + this.beforeBody = undefined; + this.body = undefined; + this.afterBody = undefined; + this.footer = undefined; + this.xAlign = undefined; + this.yAlign = undefined; + this.x = undefined; + this.y = undefined; + this.height = undefined; + this.width = undefined; + this.caretX = undefined; + this.caretY = undefined; + this.labelColors = undefined; + this.labelPointStyles = undefined; + this.labelTextColors = undefined; + } + + initialize(options) { + const defaultSize = options.bodyFont.size; + options.boxHeight = valueOrDefault(options.boxHeight, defaultSize); + options.boxWidth = valueOrDefault(options.boxWidth, defaultSize); + this.options = options; + this._cachedAnimations = undefined; + } + + /** * @private */ - _resolveAnimations() { - const me = this; - const cached = me._cachedAnimations; - - if (cached) { - return cached; - } - - const chart = me._chart; - const options = me.options; - const opts = options.enabled && chart.options.animation && options.animation; - const animations = new Animations(me._chart, opts); - me._cachedAnimations = Object.freeze(animations); - - return animations; - } - - getTitle(context) { - const me = this; - const opts = me.options; - const callbacks = opts.callbacks; - - const beforeTitle = callbacks.beforeTitle.apply(me, [context]); - const title = callbacks.title.apply(me, [context]); - const afterTitle = callbacks.afterTitle.apply(me, [context]); - - let lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeTitle)); - lines = pushOrConcat(lines, splitNewlines(title)); - lines = pushOrConcat(lines, splitNewlines(afterTitle)); - - return lines; - } - - getBeforeBody(tooltipItems) { - return getBeforeAfterBodyLines(this.options.callbacks.beforeBody.apply(this, [tooltipItems])); - } - - getBody(tooltipItems) { - const me = this; - const callbacks = me.options.callbacks; - const bodyItems = []; - - each(tooltipItems, (context) => { - const bodyItem = { - before: [], - lines: [], - after: [] - }; - pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, context))); - pushOrConcat(bodyItem.lines, callbacks.label.call(me, context)); - pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, context))); - - bodyItems.push(bodyItem); - }); - - return bodyItems; - } - - getAfterBody(tooltipItems) { - return getBeforeAfterBodyLines(this.options.callbacks.afterBody.apply(this, [tooltipItems])); - } - - // Get the footer and beforeFooter and afterFooter lines - getFooter(tooltipItems) { - const me = this; - const callbacks = me.options.callbacks; - - const beforeFooter = callbacks.beforeFooter.apply(me, [tooltipItems]); - const footer = callbacks.footer.apply(me, [tooltipItems]); - const afterFooter = callbacks.afterFooter.apply(me, [tooltipItems]); - - let lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeFooter)); - lines = pushOrConcat(lines, splitNewlines(footer)); - lines = pushOrConcat(lines, splitNewlines(afterFooter)); - - return lines; - } - - /** + _resolveAnimations() { + const me = this; + const cached = me._cachedAnimations; + + if (cached) { + return cached; + } + + const chart = me._chart; + const options = me.options; + const opts = options.enabled && chart.options.animation && options.animation; + const animations = new Animations(me._chart, opts); + me._cachedAnimations = Object.freeze(animations); + + return animations; + } + + getTitle(context) { + const me = this; + const opts = me.options; + const callbacks = opts.callbacks; + + const beforeTitle = callbacks.beforeTitle.apply(me, [context]); + const title = callbacks.title.apply(me, [context]); + const afterTitle = callbacks.afterTitle.apply(me, [context]); + + let lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeTitle)); + lines = pushOrConcat(lines, splitNewlines(title)); + lines = pushOrConcat(lines, splitNewlines(afterTitle)); + + return lines; + } + + getBeforeBody(tooltipItems) { + return getBeforeAfterBodyLines(this.options.callbacks.beforeBody.apply(this, [tooltipItems])); + } + + getBody(tooltipItems) { + const me = this; + const callbacks = me.options.callbacks; + const bodyItems = []; + + each(tooltipItems, (context) => { + const bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, context))); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, context)); + pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, context))); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + } + + getAfterBody(tooltipItems) { + return getBeforeAfterBodyLines(this.options.callbacks.afterBody.apply(this, [tooltipItems])); + } + + // Get the footer and beforeFooter and afterFooter lines + getFooter(tooltipItems) { + const me = this; + const callbacks = me.options.callbacks; + + const beforeFooter = callbacks.beforeFooter.apply(me, [tooltipItems]); + const footer = callbacks.footer.apply(me, [tooltipItems]); + const afterFooter = callbacks.afterFooter.apply(me, [tooltipItems]); + + let lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeFooter)); + lines = pushOrConcat(lines, splitNewlines(footer)); + lines = pushOrConcat(lines, splitNewlines(afterFooter)); + + return lines; + } + + /** * @private */ - _createItems() { - const me = this; - const active = me._active; - const options = me.options; - const data = me._chart.data; - const labelColors = []; - const labelPointStyles = []; - const labelTextColors = []; - let tooltipItems = []; - let i, len; - - for (i = 0, len = active.length; i < len; ++i) { - tooltipItems.push(createTooltipItem(me._chart, active[i])); - } - - // If the user provided a filter function, use it to modify the tooltip items - if (options.filter) { - tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data)); - } - - // If the user provided a sorting function, use it to modify the tooltip items - if (options.itemSort) { - tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data)); - } - - // Determine colors for boxes - each(tooltipItems, (context) => { - labelColors.push(options.callbacks.labelColor.call(me, context)); - labelPointStyles.push(options.callbacks.labelPointStyle.call(me, context)); - labelTextColors.push(options.callbacks.labelTextColor.call(me, context)); - }); - - me.labelColors = labelColors; - me.labelPointStyles = labelPointStyles; - me.labelTextColors = labelTextColors; - me.dataPoints = tooltipItems; - return tooltipItems; - } - - update(changed) { - const me = this; - const options = me.options; - const active = me._active; - let properties; - - if (!active.length) { - if (me.opacity !== 0) { - properties = { - opacity: 0 - }; - } - } else { - const position = positioners[options.position].call(me, active, me._eventPosition); - const tooltipItems = me._createItems(); - - me.title = me.getTitle(tooltipItems); - me.beforeBody = me.getBeforeBody(tooltipItems); - me.body = me.getBody(tooltipItems); - me.afterBody = me.getAfterBody(tooltipItems); - me.footer = me.getFooter(tooltipItems); - - const size = me._size = getTooltipSize(me); - const positionAndSize = Object.assign({}, position, size); - const alignment = determineAlignment(me._chart, options, positionAndSize); - const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, me._chart); - - me.xAlign = alignment.xAlign; - me.yAlign = alignment.yAlign; - - properties = { - opacity: 1, - x: backgroundPoint.x, - y: backgroundPoint.y, - width: size.width, - height: size.height, - caretX: position.x, - caretY: position.y - }; - } - - if (properties) { - me._resolveAnimations().update(me, properties); - } - - if (changed && options.custom) { - options.custom.call(me, {chart: me._chart, tooltip: me}); - } - } - - drawCaret(tooltipPoint, ctx, size) { - const caretPosition = this.getCaretPosition(tooltipPoint, size); - - ctx.lineTo(caretPosition.x1, caretPosition.y1); - ctx.lineTo(caretPosition.x2, caretPosition.y2); - ctx.lineTo(caretPosition.x3, caretPosition.y3); - } - - getCaretPosition(tooltipPoint, size) { - const {xAlign, yAlign, options} = this; - const {cornerRadius, caretSize} = options; - const {x: ptX, y: ptY} = tooltipPoint; - const {width, height} = size; - let x1, x2, x3, y1, y2, y3; - - if (yAlign === 'center') { - y2 = ptY + (height / 2); - - if (xAlign === 'left') { - x1 = ptX; - x2 = x1 - caretSize; - - // Left draws bottom -> top, this y1 is on the bottom - y1 = y2 + caretSize; - y3 = y2 - caretSize; - } else { - x1 = ptX + width; - x2 = x1 + caretSize; - - // Right draws top -> bottom, thus y1 is on the top - y1 = y2 - caretSize; - y3 = y2 + caretSize; - } - - x3 = x1; - } else { - if (xAlign === 'left') { - x2 = ptX + cornerRadius + (caretSize); - } else if (xAlign === 'right') { - x2 = ptX + width - cornerRadius - caretSize; - } else { - x2 = this.caretX; - } - - if (yAlign === 'top') { - y1 = ptY; - y2 = y1 - caretSize; - - // Top draws left -> right, thus x1 is on the left - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } else { - y1 = ptY + height; - y2 = y1 + caretSize; - - // Bottom draws right -> left, thus x1 is on the right - x1 = x2 + caretSize; - x3 = x2 - caretSize; - } - y3 = y1; - } - return {x1, x2, x3, y1, y2, y3}; - } - - drawTitle(pt, ctx) { - const me = this; - const options = me.options; - const title = me.title; - const length = title.length; - let titleFont, titleSpacing, i; - - if (length) { - const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); - - pt.x = getAlignedX(me, options.titleAlign); - - ctx.textAlign = rtlHelper.textAlign(options.titleAlign); - ctx.textBaseline = 'middle'; - - titleFont = options.titleFont; - titleSpacing = options.titleSpacing; - - ctx.fillStyle = options.titleColor; - ctx.font = toFontString(titleFont); - - for (i = 0; i < length; ++i) { - ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.size / 2); - pt.y += titleFont.size + titleSpacing; // Line Height and spacing - - if (i + 1 === length) { - pt.y += options.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing - } - } - } - } - - /** + _createItems() { + const me = this; + const active = me._active; + const options = me.options; + const data = me._chart.data; + const labelColors = []; + const labelPointStyles = []; + const labelTextColors = []; + let tooltipItems = []; + let i, len; + + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(me._chart, active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (options.filter) { + tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data)); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (options.itemSort) { + tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data)); + } + + // Determine colors for boxes + each(tooltipItems, (context) => { + labelColors.push(options.callbacks.labelColor.call(me, context)); + labelPointStyles.push(options.callbacks.labelPointStyle.call(me, context)); + labelTextColors.push(options.callbacks.labelTextColor.call(me, context)); + }); + + me.labelColors = labelColors; + me.labelPointStyles = labelPointStyles; + me.labelTextColors = labelTextColors; + me.dataPoints = tooltipItems; + return tooltipItems; + } + + update(changed) { + const me = this; + const options = me.options; + const active = me._active; + let properties; + + if (!active.length) { + if (me.opacity !== 0) { + properties = { + opacity: 0 + }; + } + } else { + const position = positioners[options.position].call(me, active, me._eventPosition); + const tooltipItems = me._createItems(); + + me.title = me.getTitle(tooltipItems); + me.beforeBody = me.getBeforeBody(tooltipItems); + me.body = me.getBody(tooltipItems); + me.afterBody = me.getAfterBody(tooltipItems); + me.footer = me.getFooter(tooltipItems); + + const size = me._size = getTooltipSize(me); + const positionAndSize = Object.assign({}, position, size); + const alignment = determineAlignment(me._chart, options, positionAndSize); + const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, me._chart); + + me.xAlign = alignment.xAlign; + me.yAlign = alignment.yAlign; + + properties = { + opacity: 1, + x: backgroundPoint.x, + y: backgroundPoint.y, + width: size.width, + height: size.height, + caretX: position.x, + caretY: position.y + }; + } + + if (properties) { + me._resolveAnimations().update(me, properties); + } + + if (changed && options.custom) { + options.custom.call(me, {chart: me._chart, tooltip: me}); + } + } + + drawCaret(tooltipPoint, ctx, size) { + const caretPosition = this.getCaretPosition(tooltipPoint, size); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + } + + getCaretPosition(tooltipPoint, size) { + const {xAlign, yAlign, options} = this; + const {cornerRadius, caretSize} = options; + const {x: ptX, y: ptY} = tooltipPoint; + const {width, height} = size; + let x1, x2, x3, y1, y2, y3; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + + // Left draws bottom -> top, this y1 is on the bottom + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + + // Right draws top -> bottom, thus y1 is on the top + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + + x3 = x1; + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + } else { + x2 = this.caretX; + } + + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + + // Top draws left -> right, thus x1 is on the left + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + + // Bottom draws right -> left, thus x1 is on the right + x1 = x2 + caretSize; + x3 = x2 - caretSize; + } + y3 = y1; + } + return {x1, x2, x3, y1, y2, y3}; + } + + drawTitle(pt, ctx) { + const me = this; + const options = me.options; + const title = me.title; + const length = title.length; + let titleFont, titleSpacing, i; + + if (length) { + const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); + + pt.x = getAlignedX(me, options.titleAlign); + + ctx.textAlign = rtlHelper.textAlign(options.titleAlign); + ctx.textBaseline = 'middle'; + + titleFont = options.titleFont; + titleSpacing = options.titleSpacing; + + ctx.fillStyle = options.titleColor; + ctx.font = toFontString(titleFont); + + for (i = 0; i < length; ++i) { + ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.size / 2); + pt.y += titleFont.size + titleSpacing; // Line Height and spacing + + if (i + 1 === length) { + pt.y += options.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + } + + /** * @private */ - _drawColorBox(ctx, pt, i, rtlHelper) { - const me = this; - const options = me.options; - const labelColors = me.labelColors[i]; - const labelPointStyle = me.labelPointStyles[i]; - const {boxHeight, boxWidth, bodyFont} = options; - const colorX = getAlignedX(me, 'left'); - const rtlColorX = rtlHelper.x(colorX); - const yOffSet = boxHeight < bodyFont.size ? (bodyFont.size - boxHeight) / 2 : 0; - const colorY = pt.y + yOffSet; - - if (options.usePointStyle) { - const drawOptions = { - radius: Math.min(boxWidth, boxHeight) / 2, // fit the circle in the box - pointStyle: labelPointStyle.pointStyle, - rotation: labelPointStyle.rotation, - borderWidth: 1 - }; - // Recalculate x and y for drawPoint() because its expecting - // x and y to be center of figure (instead of top left) - const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2; - const centerY = colorY + boxHeight / 2; - - // Fill the point with white so that colours merge nicely if the opacity is < 1 - ctx.strokeStyle = options.multiKeyBackground; - ctx.fillStyle = options.multiKeyBackground; - drawPoint(ctx, drawOptions, centerX, centerY); - - // Draw the point - ctx.strokeStyle = labelColors.borderColor; - ctx.fillStyle = labelColors.backgroundColor; - drawPoint(ctx, drawOptions, centerX, centerY); - } else { - // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = options.multiKeyBackground; - ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); - - // Border - ctx.lineWidth = 1; - ctx.strokeStyle = labelColors.borderColor; - ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); - - // Inner square - ctx.fillStyle = labelColors.backgroundColor; - ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2), colorY + 1, boxWidth - 2, boxHeight - 2); - } - - // restore fillStyle - ctx.fillStyle = me.labelTextColors[i]; - } - - drawBody(pt, ctx) { - const me = this; - const {body, options} = me; - const {bodyFont, bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth} = options; - let bodyLineHeight = bodyFont.size; - let xLinePadding = 0; - - const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); - - const fillLineOfText = function(line) { - ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2); - pt.y += bodyLineHeight + bodySpacing; - }; - - const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); - let bodyItem, textColor, lines, i, j, ilen, jlen; - - ctx.textAlign = bodyAlign; - ctx.textBaseline = 'middle'; - ctx.font = toFontString(bodyFont); - - pt.x = getAlignedX(me, bodyAlignForCalculation); - - // Before body lines - ctx.fillStyle = options.bodyColor; - each(me.beforeBody, fillLineOfText); - - xLinePadding = displayColors && bodyAlignForCalculation !== 'right' - ? bodyAlign === 'center' ? (boxWidth / 2 + 1) : (boxWidth + 2) - : 0; - - // Draw body lines now - for (i = 0, ilen = body.length; i < ilen; ++i) { - bodyItem = body[i]; - textColor = me.labelTextColors[i]; - - ctx.fillStyle = textColor; - each(bodyItem.before, fillLineOfText); - - lines = bodyItem.lines; - // Draw Legend-like boxes if needed - if (displayColors && lines.length) { - me._drawColorBox(ctx, pt, i, rtlHelper); - bodyLineHeight = Math.max(bodyFont.size, boxHeight); - } - - for (j = 0, jlen = lines.length; j < jlen; ++j) { - fillLineOfText(lines[j]); - // Reset for any lines that don't include colorbox - bodyLineHeight = bodyFont.size; - } - - each(bodyItem.after, fillLineOfText); - } - - // Reset back to 0 for after body - xLinePadding = 0; - bodyLineHeight = bodyFont.size; - - // After body lines - each(me.afterBody, fillLineOfText); - pt.y -= bodySpacing; // Remove last body spacing - } - - drawFooter(pt, ctx) { - const me = this; - const options = me.options; - const footer = me.footer; - const length = footer.length; - let footerFont, i; - - if (length) { - const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); - - pt.x = getAlignedX(me, options.footerAlign); - pt.y += options.footerMarginTop; - - ctx.textAlign = rtlHelper.textAlign(options.footerAlign); - ctx.textBaseline = 'middle'; - - footerFont = options.footerFont; - - ctx.fillStyle = options.footerColor; - ctx.font = toFontString(footerFont); - - for (i = 0; i < length; ++i) { - ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.size / 2); - pt.y += footerFont.size + options.footerSpacing; - } - } - } - - drawBackground(pt, ctx, tooltipSize) { - const {xAlign, yAlign, options} = this; - const {x, y} = pt; - const {width, height} = tooltipSize; - const radius = options.cornerRadius; - - ctx.fillStyle = options.backgroundColor; - ctx.strokeStyle = options.borderColor; - ctx.lineWidth = options.borderWidth; - - ctx.beginPath(); - ctx.moveTo(x + radius, y); - if (yAlign === 'top') { - this.drawCaret(pt, ctx, tooltipSize); - } - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - if (yAlign === 'center' && xAlign === 'right') { - this.drawCaret(pt, ctx, tooltipSize); - } - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - if (yAlign === 'bottom') { - this.drawCaret(pt, ctx, tooltipSize); - } - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - if (yAlign === 'center' && xAlign === 'left') { - this.drawCaret(pt, ctx, tooltipSize); - } - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - - ctx.fill(); - - if (options.borderWidth > 0) { - ctx.stroke(); - } - } - - /** + _drawColorBox(ctx, pt, i, rtlHelper) { + const me = this; + const options = me.options; + const labelColors = me.labelColors[i]; + const labelPointStyle = me.labelPointStyles[i]; + const {boxHeight, boxWidth, bodyFont} = options; + const colorX = getAlignedX(me, 'left'); + const rtlColorX = rtlHelper.x(colorX); + const yOffSet = boxHeight < bodyFont.size ? (bodyFont.size - boxHeight) / 2 : 0; + const colorY = pt.y + yOffSet; + + if (options.usePointStyle) { + const drawOptions = { + radius: Math.min(boxWidth, boxHeight) / 2, // fit the circle in the box + pointStyle: labelPointStyle.pointStyle, + rotation: labelPointStyle.rotation, + borderWidth: 1 + }; + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2; + const centerY = colorY + boxHeight / 2; + + // Fill the point with white so that colours merge nicely if the opacity is < 1 + ctx.strokeStyle = options.multiKeyBackground; + ctx.fillStyle = options.multiKeyBackground; + drawPoint(ctx, drawOptions, centerX, centerY); + + // Draw the point + ctx.strokeStyle = labelColors.borderColor; + ctx.fillStyle = labelColors.backgroundColor; + drawPoint(ctx, drawOptions, centerX, centerY); + } else { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = options.multiKeyBackground; + ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = labelColors.borderColor; + ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); + + // Inner square + ctx.fillStyle = labelColors.backgroundColor; + ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2), colorY + 1, boxWidth - 2, boxHeight - 2); + } + + // restore fillStyle + ctx.fillStyle = me.labelTextColors[i]; + } + + drawBody(pt, ctx) { + const me = this; + const {body, options} = me; + const {bodyFont, bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth} = options; + let bodyLineHeight = bodyFont.size; + let xLinePadding = 0; + + const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); + + const fillLineOfText = function(line) { + ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2); + pt.y += bodyLineHeight + bodySpacing; + }; + + const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); + let bodyItem, textColor, lines, i, j, ilen, jlen; + + ctx.textAlign = bodyAlign; + ctx.textBaseline = 'middle'; + ctx.font = toFontString(bodyFont); + + pt.x = getAlignedX(me, bodyAlignForCalculation); + + // Before body lines + ctx.fillStyle = options.bodyColor; + each(me.beforeBody, fillLineOfText); + + xLinePadding = displayColors && bodyAlignForCalculation !== 'right' + ? bodyAlign === 'center' ? (boxWidth / 2 + 1) : (boxWidth + 2) + : 0; + + // Draw body lines now + for (i = 0, ilen = body.length; i < ilen; ++i) { + bodyItem = body[i]; + textColor = me.labelTextColors[i]; + + ctx.fillStyle = textColor; + each(bodyItem.before, fillLineOfText); + + lines = bodyItem.lines; + // Draw Legend-like boxes if needed + if (displayColors && lines.length) { + me._drawColorBox(ctx, pt, i, rtlHelper); + bodyLineHeight = Math.max(bodyFont.size, boxHeight); + } + + for (j = 0, jlen = lines.length; j < jlen; ++j) { + fillLineOfText(lines[j]); + // Reset for any lines that don't include colorbox + bodyLineHeight = bodyFont.size; + } + + each(bodyItem.after, fillLineOfText); + } + + // Reset back to 0 for after body + xLinePadding = 0; + bodyLineHeight = bodyFont.size; + + // After body lines + each(me.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + } + + drawFooter(pt, ctx) { + const me = this; + const options = me.options; + const footer = me.footer; + const length = footer.length; + let footerFont, i; + + if (length) { + const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width); + + pt.x = getAlignedX(me, options.footerAlign); + pt.y += options.footerMarginTop; + + ctx.textAlign = rtlHelper.textAlign(options.footerAlign); + ctx.textBaseline = 'middle'; + + footerFont = options.footerFont; + + ctx.fillStyle = options.footerColor; + ctx.font = toFontString(footerFont); + + for (i = 0; i < length; ++i) { + ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.size / 2); + pt.y += footerFont.size + options.footerSpacing; + } + } + } + + drawBackground(pt, ctx, tooltipSize) { + const {xAlign, yAlign, options} = this; + const {x, y} = pt; + const {width, height} = tooltipSize; + const radius = options.cornerRadius; + + ctx.fillStyle = options.backgroundColor; + ctx.strokeStyle = options.borderColor; + ctx.lineWidth = options.borderWidth; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, ctx, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, ctx, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, ctx, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, ctx, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (options.borderWidth > 0) { + ctx.stroke(); + } + } + + /** * Update x/y animation targets when _active elements are animating too * @private */ - _updateAnimationTarget() { - const me = this; - const chart = me._chart; - const options = me.options; - const anims = me.$animations; - const animX = anims && anims.x; - const animY = anims && anims.y; - if (animX || animY) { - const position = positioners[options.position].call(me, me._active, me._eventPosition); - if (!position) { - return; - } - const size = me._size = getTooltipSize(me); - const positionAndSize = Object.assign({}, position, me._size); - const alignment = determineAlignment(chart, options, positionAndSize); - const point = getBackgroundPoint(options, positionAndSize, alignment, chart); - if (animX._to !== point.x || animY._to !== point.y) { - me.xAlign = alignment.xAlign; - me.yAlign = alignment.yAlign; - me.width = size.width; - me.height = size.height; - me.caretX = position.x; - me.caretY = position.y; - me._resolveAnimations().update(me, point); - } - } - } - - draw(ctx) { - const me = this; - const options = me.options; - let opacity = me.opacity; - - if (!opacity) { - return; - } - - me._updateAnimationTarget(); - - const tooltipSize = { - width: me.width, - height: me.height - }; - const pt = { - x: me.x, - y: me.y - }; - - // IE11/Edge does not like very small opacities, so snap to 0 - opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity; - - // Truthy/falsey value for empty tooltip - const hasTooltipContent = me.title.length || me.beforeBody.length || me.body.length || me.afterBody.length || me.footer.length; - - if (options.enabled && hasTooltipContent) { - ctx.save(); - ctx.globalAlpha = opacity; - - // Draw Background - me.drawBackground(pt, ctx, tooltipSize); - - overrideTextDirection(ctx, options.textDirection); - - pt.y += options.yPadding; - - // Titles - me.drawTitle(pt, ctx); - - // Body - me.drawBody(pt, ctx); - - // Footer - me.drawFooter(pt, ctx); - - restoreTextDirection(ctx, options.textDirection); - - ctx.restore(); - } - } - - /** + _updateAnimationTarget() { + const me = this; + const chart = me._chart; + const options = me.options; + const anims = me.$animations; + const animX = anims && anims.x; + const animY = anims && anims.y; + if (animX || animY) { + const position = positioners[options.position].call(me, me._active, me._eventPosition); + if (!position) { + return; + } + const size = me._size = getTooltipSize(me); + const positionAndSize = Object.assign({}, position, me._size); + const alignment = determineAlignment(chart, options, positionAndSize); + const point = getBackgroundPoint(options, positionAndSize, alignment, chart); + if (animX._to !== point.x || animY._to !== point.y) { + me.xAlign = alignment.xAlign; + me.yAlign = alignment.yAlign; + me.width = size.width; + me.height = size.height; + me.caretX = position.x; + me.caretY = position.y; + me._resolveAnimations().update(me, point); + } + } + } + + draw(ctx) { + const me = this; + const options = me.options; + let opacity = me.opacity; + + if (!opacity) { + return; + } + + me._updateAnimationTarget(); + + const tooltipSize = { + width: me.width, + height: me.height + }; + const pt = { + x: me.x, + y: me.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity; + + // Truthy/falsey value for empty tooltip + const hasTooltipContent = me.title.length || me.beforeBody.length || me.body.length || me.afterBody.length || me.footer.length; + + if (options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + + // Draw Background + me.drawBackground(pt, ctx, tooltipSize); + + overrideTextDirection(ctx, options.textDirection); + + pt.y += options.yPadding; + + // Titles + me.drawTitle(pt, ctx); + + // Body + me.drawBody(pt, ctx); + + // Footer + me.drawFooter(pt, ctx); + + restoreTextDirection(ctx, options.textDirection); + + ctx.restore(); + } + } + + /** * Get active elements in the tooltip * @returns {Array} Array of elements that are active in the tooltip */ - getActiveElements() { - return this._active || []; - } + getActiveElements() { + return this._active || []; + } - /** + /** * Set active elements in the tooltip * @param {array} activeElements Array of active datasetIndex/index pairs. * @param {object} eventPosition Synthetic event position used in positioning */ - setActiveElements(activeElements, eventPosition) { - const me = this; - const lastActive = me._active; - const active = activeElements.map(({datasetIndex, index}) => { - const meta = me._chart.getDatasetMeta(datasetIndex); - - if (!meta) { - throw new Error('Cannot find a dataset at index ' + datasetIndex); - } - - return { - datasetIndex, - element: meta.data[index], - index, - }; - }); - const changed = !_elementsEqual(lastActive, active); - const positionChanged = me._positionChanged(active, eventPosition); - - if (changed || positionChanged) { - me._active = active; - me._eventPosition = eventPosition; - me.update(true); - } - } - - /** + setActiveElements(activeElements, eventPosition) { + const me = this; + const lastActive = me._active; + const active = activeElements.map(({datasetIndex, index}) => { + const meta = me._chart.getDatasetMeta(datasetIndex); + + if (!meta) { + throw new Error('Cannot find a dataset at index ' + datasetIndex); + } + + return { + datasetIndex, + element: meta.data[index], + index, + }; + }); + const changed = !_elementsEqual(lastActive, active); + const positionChanged = me._positionChanged(active, eventPosition); + + if (changed || positionChanged) { + me._active = active; + me._eventPosition = eventPosition; + me.update(true); + } + } + + /** * Handle an event * @param {ChartEvent} e - The event to handle * @param {boolean} [replay] - This is a replayed event (from update) * @returns {boolean} true if the tooltip changed */ - handleEvent(e, replay) { - const me = this; - const options = me.options; - const lastActive = me._active || []; - let changed = false; - let active = []; - - // Find Active Elements for tooltips - if (e.type !== 'mouseout') { - active = me._chart.getElementsAtEventForMode(e, options.mode, options, replay); - if (options.reverse) { - active.reverse(); - } - } - - // When there are multiple items shown, but the tooltip position is nearest mode - // an update may need to be made because our position may have changed even though - // the items are the same as before. - const positionChanged = me._positionChanged(active, e); - - // Remember Last Actives - changed = replay || !_elementsEqual(active, lastActive) || positionChanged; - - // Only handle target event on tooltip change - if (changed) { - me._active = active; - - if (options.enabled || options.custom) { - me._eventPosition = { - x: e.x, - y: e.y - }; - - me.update(true); - } - } - - return changed; - } - - /** + handleEvent(e, replay) { + const me = this; + const options = me.options; + const lastActive = me._active || []; + let changed = false; + let active = []; + + // Find Active Elements for tooltips + if (e.type !== 'mouseout') { + active = me._chart.getElementsAtEventForMode(e, options.mode, options, replay); + if (options.reverse) { + active.reverse(); + } + } + + // When there are multiple items shown, but the tooltip position is nearest mode + // an update may need to be made because our position may have changed even though + // the items are the same as before. + const positionChanged = me._positionChanged(active, e); + + // Remember Last Actives + changed = replay || !_elementsEqual(active, lastActive) || positionChanged; + + // Only handle target event on tooltip change + if (changed) { + me._active = active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + me.update(true); + } + } + + return changed; + } + + /** * Determine if the active elements + event combination changes the * tooltip position * @param {array} active - Active elements * @param {ChartEvent} e - Event that triggered the position change * @returns {boolean} True if the position has changed */ - _positionChanged(active, e) { - const me = this; - const position = positioners[me.options.position].call(me, active, e); - return me.caretX !== position.x || me.caretY !== position.y; - } + _positionChanged(active, e) { + const me = this; + const position = positioners[me.options.position].call(me, active, e); + return me.caretX !== position.x || me.caretY !== position.y; + } } /** @@ -1022,179 +1022,179 @@ export class Tooltip extends Element { Tooltip.positioners = positioners; export default { - id: 'tooltip', - _element: Tooltip, - positioners, - - afterInit(chart, _args, options) { - if (options) { - chart.tooltip = new Tooltip({_chart: chart, options}); - } - }, - - beforeUpdate(chart, _args, options) { - if (chart.tooltip) { - chart.tooltip.initialize(options); - } - }, - - reset(chart, _args, options) { - if (chart.tooltip) { - chart.tooltip.initialize(options); - } - }, - - afterDraw(chart) { - const tooltip = chart.tooltip; - - const args = { - tooltip - }; - - if (chart.notifyPlugins('beforeTooltipDraw', args) === false) { - return; - } - - if (tooltip) { - tooltip.draw(chart.ctx); - } - - chart.notifyPlugins('afterTooltipDraw', args); - }, - - afterEvent(chart, args) { - if (chart.tooltip) { - // If the event is replayed from `update`, we should evaluate with the final positions. - const useFinalPosition = args.replay; - if (chart.tooltip.handleEvent(args.event, useFinalPosition)) { - // notify chart about the change, so it will render - args.changed = true; - } - } - }, - - defaults: { - enabled: true, - custom: null, - position: 'average', - backgroundColor: 'rgba(0,0,0,0.8)', - titleColor: '#fff', - titleFont: { - style: 'bold', - }, - titleSpacing: 2, - titleMarginBottom: 6, - titleAlign: 'left', - bodyColor: '#fff', - bodySpacing: 2, - bodyFont: { - }, - bodyAlign: 'left', - footerColor: '#fff', - footerSpacing: 2, - footerMarginTop: 6, - footerFont: { - style: 'bold', - }, - footerAlign: 'left', - yPadding: 6, - xPadding: 6, - caretPadding: 2, - caretSize: 5, - cornerRadius: 6, - multiKeyBackground: '#fff', - displayColors: true, - borderColor: 'rgba(0,0,0,0)', - borderWidth: 0, - animation: { - duration: 400, - easing: 'easeOutQuart', - numbers: { - type: 'number', - properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'], - }, - opacity: { - easing: 'linear', - duration: 200 - } - }, - callbacks: { - // Args are: (tooltipItems, data) - beforeTitle: noop, - title(tooltipItems) { - if (tooltipItems.length > 0) { - const item = tooltipItems[0]; - const labels = item.chart.data.labels; - const labelCount = labels ? labels.length : 0; - - if (this && this.options && this.options.mode === 'dataset') { - return item.dataset.label || ''; - } else if (item.label) { - return item.label; - } else if (labelCount > 0 && item.dataIndex < labelCount) { - return labels[item.dataIndex]; - } - } - - return ''; - }, - afterTitle: noop, - - // Args are: (tooltipItems, data) - beforeBody: noop, - - // Args are: (tooltipItem, data) - beforeLabel: noop, - label(tooltipItem) { - if (this && this.options && this.options.mode === 'dataset') { - return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue; - } - - let label = tooltipItem.dataset.label || ''; - - if (label) { - label += ': '; - } - const value = tooltipItem.formattedValue; - if (!isNullOrUndef(value)) { - label += value; - } - return label; - }, - labelColor(tooltipItem) { - const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); - const options = meta.controller.getStyle(tooltipItem.dataIndex); - return { - borderColor: options.borderColor, - backgroundColor: options.backgroundColor - }; - }, - labelTextColor() { - return this.options.bodyColor; - }, - labelPointStyle(tooltipItem) { - const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); - const options = meta.controller.getStyle(tooltipItem.dataIndex); - return { - pointStyle: options.pointStyle, - rotation: options.rotation, - }; - }, - afterLabel: noop, - - // Args are: (tooltipItems, data) - afterBody: noop, - - // Args are: (tooltipItems, data) - beforeFooter: noop, - footer: noop, - afterFooter: noop - } - }, - - defaultRoutes: { - bodyFont: 'font', - footerFont: 'font', - titleFont: 'font' - } + id: 'tooltip', + _element: Tooltip, + positioners, + + afterInit(chart, _args, options) { + if (options) { + chart.tooltip = new Tooltip({_chart: chart, options}); + } + }, + + beforeUpdate(chart, _args, options) { + if (chart.tooltip) { + chart.tooltip.initialize(options); + } + }, + + reset(chart, _args, options) { + if (chart.tooltip) { + chart.tooltip.initialize(options); + } + }, + + afterDraw(chart) { + const tooltip = chart.tooltip; + + const args = { + tooltip + }; + + if (chart.notifyPlugins('beforeTooltipDraw', args) === false) { + return; + } + + if (tooltip) { + tooltip.draw(chart.ctx); + } + + chart.notifyPlugins('afterTooltipDraw', args); + }, + + afterEvent(chart, args) { + if (chart.tooltip) { + // If the event is replayed from `update`, we should evaluate with the final positions. + const useFinalPosition = args.replay; + if (chart.tooltip.handleEvent(args.event, useFinalPosition)) { + // notify chart about the change, so it will render + args.changed = true; + } + } + }, + + defaults: { + enabled: true, + custom: null, + position: 'average', + backgroundColor: 'rgba(0,0,0,0.8)', + titleColor: '#fff', + titleFont: { + style: 'bold', + }, + titleSpacing: 2, + titleMarginBottom: 6, + titleAlign: 'left', + bodyColor: '#fff', + bodySpacing: 2, + bodyFont: { + }, + bodyAlign: 'left', + footerColor: '#fff', + footerSpacing: 2, + footerMarginTop: 6, + footerFont: { + style: 'bold', + }, + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + animation: { + duration: 400, + easing: 'easeOutQuart', + numbers: { + type: 'number', + properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'], + }, + opacity: { + easing: 'linear', + duration: 200 + } + }, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: noop, + title(tooltipItems) { + if (tooltipItems.length > 0) { + const item = tooltipItems[0]; + const labels = item.chart.data.labels; + const labelCount = labels ? labels.length : 0; + + if (this && this.options && this.options.mode === 'dataset') { + return item.dataset.label || ''; + } else if (item.label) { + return item.label; + } else if (labelCount > 0 && item.dataIndex < labelCount) { + return labels[item.dataIndex]; + } + } + + return ''; + }, + afterTitle: noop, + + // Args are: (tooltipItems, data) + beforeBody: noop, + + // Args are: (tooltipItem, data) + beforeLabel: noop, + label(tooltipItem) { + if (this && this.options && this.options.mode === 'dataset') { + return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue; + } + + let label = tooltipItem.dataset.label || ''; + + if (label) { + label += ': '; + } + const value = tooltipItem.formattedValue; + if (!isNullOrUndef(value)) { + label += value; + } + return label; + }, + labelColor(tooltipItem) { + const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); + const options = meta.controller.getStyle(tooltipItem.dataIndex); + return { + borderColor: options.borderColor, + backgroundColor: options.backgroundColor + }; + }, + labelTextColor() { + return this.options.bodyColor; + }, + labelPointStyle(tooltipItem) { + const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); + const options = meta.controller.getStyle(tooltipItem.dataIndex); + return { + pointStyle: options.pointStyle, + rotation: options.rotation, + }; + }, + afterLabel: noop, + + // Args are: (tooltipItems, data) + afterBody: noop, + + // Args are: (tooltipItems, data) + beforeFooter: noop, + footer: noop, + afterFooter: noop + } + }, + + defaultRoutes: { + bodyFont: 'font', + footerFont: 'font', + titleFont: 'font' + } }; diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index 6c06719e847..868b24b5c81 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -1,122 +1,122 @@ import Scale from '../core/core.scale'; function findOrAddLabel(labels, raw, index) { - const first = labels.indexOf(raw); - if (first === -1) { - return typeof raw === 'string' ? labels.push(raw) - 1 : index; - } - const last = labels.lastIndexOf(raw); - return first !== last ? index : first; + const first = labels.indexOf(raw); + if (first === -1) { + return typeof raw === 'string' ? labels.push(raw) - 1 : index; + } + const last = labels.lastIndexOf(raw); + return first !== last ? index : first; } export default class CategoryScale extends Scale { - constructor(cfg) { - super(cfg); - - /** @type {number} */ - this._startValue = undefined; - this._valueRange = 0; - } - - parse(raw, index) { - const labels = this.getLabels(); - return isFinite(index) && labels[index] === raw - ? index : findOrAddLabel(labels, raw, index); - } - - determineDataLimits() { - const me = this; - const {minDefined, maxDefined} = me.getUserBounds(); - let {min, max} = me.getMinMax(true); - - if (me.options.bounds === 'ticks') { - if (!minDefined) { - min = 0; - } - if (!maxDefined) { - max = me.getLabels().length - 1; - } - } - - me.min = min; - me.max = max; - } - - buildTicks() { - const me = this; - const min = me.min; - const max = me.max; - const offset = me.options.offset; - const ticks = []; - let labels = me.getLabels(); - - // If we are viewing some subset of labels, slice the original array - labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1); - - me._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1); - me._startValue = me.min - (offset ? 0.5 : 0); - - for (let value = min; value <= max; value++) { - ticks.push({value}); - } - return ticks; - } - - getLabelForValue(value) { - const me = this; - const labels = me.getLabels(); - - if (value >= 0 && value < labels.length) { - return labels[value]; - } - return value; - } - - /** + constructor(cfg) { + super(cfg); + + /** @type {number} */ + this._startValue = undefined; + this._valueRange = 0; + } + + parse(raw, index) { + const labels = this.getLabels(); + return isFinite(index) && labels[index] === raw + ? index : findOrAddLabel(labels, raw, index); + } + + determineDataLimits() { + const me = this; + const {minDefined, maxDefined} = me.getUserBounds(); + let {min, max} = me.getMinMax(true); + + if (me.options.bounds === 'ticks') { + if (!minDefined) { + min = 0; + } + if (!maxDefined) { + max = me.getLabels().length - 1; + } + } + + me.min = min; + me.max = max; + } + + buildTicks() { + const me = this; + const min = me.min; + const max = me.max; + const offset = me.options.offset; + const ticks = []; + let labels = me.getLabels(); + + // If we are viewing some subset of labels, slice the original array + labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1); + + me._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1); + me._startValue = me.min - (offset ? 0.5 : 0); + + for (let value = min; value <= max; value++) { + ticks.push({value}); + } + return ticks; + } + + getLabelForValue(value) { + const me = this; + const labels = me.getLabels(); + + if (value >= 0 && value < labels.length) { + return labels[value]; + } + return value; + } + + /** * @protected */ - configure() { - const me = this; - - super.configure(); - - if (!me.isHorizontal()) { - // For backward compatibility, vertical category scale reverse is inverted. - me._reversePixels = !me._reversePixels; - } - } - - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue(value) { - const me = this; - - if (typeof value !== 'number') { - value = me.parse(value); - } - - return me.getPixelForDecimal((value - me._startValue) / me._valueRange); - } - - // Must override base implementation because it calls getPixelForValue - // and category scale can have duplicate values - getPixelForTick(index) { - const me = this; - const ticks = me.ticks; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return me.getPixelForValue(ticks[index].value); - } - - getValueForPixel(pixel) { - const me = this; - return Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); - } - - getBasePixel() { - return this.bottom; - } + configure() { + const me = this; + + super.configure(); + + if (!me.isHorizontal()) { + // For backward compatibility, vertical category scale reverse is inverted. + me._reversePixels = !me._reversePixels; + } + } + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue(value) { + const me = this; + + if (typeof value !== 'number') { + value = me.parse(value); + } + + return me.getPixelForDecimal((value - me._startValue) / me._valueRange); + } + + // Must override base implementation because it calls getPixelForValue + // and category scale can have duplicate values + getPixelForTick(index) { + const me = this; + const ticks = me.ticks; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return me.getPixelForValue(ticks[index].value); + } + + getValueForPixel(pixel) { + const me = this; + return Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); + } + + getBasePixel() { + return this.bottom; + } } CategoryScale.id = 'category'; @@ -125,7 +125,7 @@ CategoryScale.id = 'category'; * @type {any} */ CategoryScale.defaults = { - ticks: { - callback: CategoryScale.prototype.getLabelForValue - } + ticks: { + callback: CategoryScale.prototype.getLabelForValue + } }; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 8c14d0e225d..ece759902fb 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -4,39 +4,39 @@ import Ticks from '../core/core.ticks'; export default class LinearScale extends LinearScaleBase { - determineDataLimits() { - const me = this; - const {min, max} = me.getMinMax(true); + determineDataLimits() { + const me = this; + const {min, max} = me.getMinMax(true); - me.min = isFinite(min) ? min : 0; - me.max = isFinite(max) ? max : 1; + me.min = isFinite(min) ? min : 0; + me.max = isFinite(max) ? max : 1; - // Common base implementation to handle min, max, beginAtZero - me.handleTickRangeOptions(); - } + // Common base implementation to handle min, max, beginAtZero + me.handleTickRangeOptions(); + } - /** + /** * Returns the maximum number of ticks based on the scale dimension * @protected */ - computeTickLimit() { - const me = this; - - if (me.isHorizontal()) { - return Math.ceil(me.width / 40); - } - const tickFont = me._resolveTickFontOptions(0); - return Math.ceil(me.height / tickFont.lineHeight); - } - - // Utils - getPixelForValue(value) { - return this.getPixelForDecimal((value - this._startValue) / this._valueRange); - } - - getValueForPixel(pixel) { - return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; - } + computeTickLimit() { + const me = this; + + if (me.isHorizontal()) { + return Math.ceil(me.width / 40); + } + const tickFont = me._resolveTickFontOptions(0); + return Math.ceil(me.height / tickFont.lineHeight); + } + + // Utils + getPixelForValue(value) { + return this.getPixelForDecimal((value - this._startValue) / this._valueRange); + } + + getValueForPixel(pixel) { + return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; + } } LinearScale.id = 'linear'; @@ -45,7 +45,7 @@ LinearScale.id = 'linear'; * @type {any} */ LinearScale.defaults = { - ticks: { - callback: Ticks.formatters.numeric - } + ticks: { + callback: Ticks.formatters.numeric + } }; diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 0ea2b9d1cef..54628a5fc01 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -8,21 +8,21 @@ import {formatNumber} from '../core/core.intl'; * @return {number} */ function niceNum(range) { - const exponent = Math.floor(log10(range)); - const fraction = range / Math.pow(10, exponent); - let niceFraction; - - if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - - return niceFraction * Math.pow(10, exponent); + const exponent = Math.floor(log10(range)); + const fraction = range / Math.pow(10, exponent); + let niceFraction; + + if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); } /** @@ -32,219 +32,219 @@ function niceNum(range) { * @returns {object[]} array of tick objects */ function generateTicks(generationOptions, dataRange) { - const ticks = []; - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. - - const MIN_SPACING = 1e-14; - const {stepSize, min, max, precision} = generationOptions; - const unit = stepSize || 1; - const maxNumSpaces = generationOptions.maxTicks - 1; - const {min: rmin, max: rmax} = dataRange; - let spacing = niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; - let factor, niceMin, niceMax, numSpaces; - - // Beyond MIN_SPACING floating point numbers being to lose precision - // such that we can't do the math necessary to generate ticks - if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) { - return [{value: rmin}, {value: rmax}]; - } - - numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); - if (numSpaces > maxNumSpaces) { - // If the calculated num of spaces exceeds maxNumSpaces, recalculate it - spacing = niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; - } - - if (stepSize || isNullOrUndef(precision)) { - // If a precision is not specified, calculate factor based on spacing - factor = Math.pow(10, _decimalPlaces(spacing)); - } else { - // If the user specified a precision, round to that number of decimal places - factor = Math.pow(10, precision); - spacing = Math.ceil(spacing * factor) / factor; - } - - niceMin = Math.floor(rmin / spacing) * spacing; - niceMax = Math.ceil(rmax / spacing) * spacing; - - // If min, max and stepSize is set and they make an evenly spaced scale use it. - if (stepSize && !isNullOrUndef(min) && !isNullOrUndef(max)) { - // If very close to our whole number, use it. - if (almostWhole((max - min) / stepSize, spacing / 1000)) { - niceMin = min; - niceMax = max; - } - } - - numSpaces = (niceMax - niceMin) / spacing; - // If very close to our rounded value, use it. - if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { - numSpaces = Math.round(numSpaces); - } else { - numSpaces = Math.ceil(numSpaces); - } - - niceMin = Math.round(niceMin * factor) / factor; - niceMax = Math.round(niceMax * factor) / factor; - ticks.push({value: isNullOrUndef(min) ? niceMin : min}); - for (let j = 1; j < numSpaces; ++j) { - ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor}); - } - ticks.push({value: isNullOrUndef(max) ? niceMax : max}); - - return ticks; + const ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + const MIN_SPACING = 1e-14; + const {stepSize, min, max, precision} = generationOptions; + const unit = stepSize || 1; + const maxNumSpaces = generationOptions.maxTicks - 1; + const {min: rmin, max: rmax} = dataRange; + let spacing = niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; + let factor, niceMin, niceMax, numSpaces; + + // Beyond MIN_SPACING floating point numbers being to lose precision + // such that we can't do the math necessary to generate ticks + if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) { + return [{value: rmin}, {value: rmax}]; + } + + numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); + if (numSpaces > maxNumSpaces) { + // If the calculated num of spaces exceeds maxNumSpaces, recalculate it + spacing = niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; + } + + if (stepSize || isNullOrUndef(precision)) { + // If a precision is not specified, calculate factor based on spacing + factor = Math.pow(10, _decimalPlaces(spacing)); + } else { + // If the user specified a precision, round to that number of decimal places + factor = Math.pow(10, precision); + spacing = Math.ceil(spacing * factor) / factor; + } + + niceMin = Math.floor(rmin / spacing) * spacing; + niceMax = Math.ceil(rmax / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (stepSize && !isNullOrUndef(min) && !isNullOrUndef(max)) { + // If very close to our whole number, use it. + if (almostWhole((max - min) / stepSize, spacing / 1000)) { + niceMin = min; + niceMax = max; + } + } + + numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + niceMin = Math.round(niceMin * factor) / factor; + niceMax = Math.round(niceMax * factor) / factor; + ticks.push({value: isNullOrUndef(min) ? niceMin : min}); + for (let j = 1; j < numSpaces; ++j) { + ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor}); + } + ticks.push({value: isNullOrUndef(max) ? niceMax : max}); + + return ticks; } export default class LinearScaleBase extends Scale { - constructor(cfg) { - super(cfg); - - /** @type {number} */ - this.start = undefined; - /** @type {number} */ - this.end = undefined; - /** @type {number} */ - this._startValue = undefined; - /** @type {number} */ - this._endValue = undefined; - this._valueRange = 0; - } - - parse(raw, index) { // eslint-disable-line no-unused-vars - if (isNullOrUndef(raw)) { - return NaN; - } - if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) { - return NaN; - } - - return +raw; - } - - handleTickRangeOptions() { - const me = this; - const {beginAtZero, stacked} = me.options; - const {minDefined, maxDefined} = me.getUserBounds(); - let {min, max} = me; - - const setMin = v => (min = minDefined ? min : v); - const setMax = v => (max = maxDefined ? max : v); - - if (beginAtZero || stacked) { - const minSign = sign(min); - const maxSign = sign(max); - - if (minSign < 0 && maxSign < 0) { - setMax(0); - } else if (minSign > 0 && maxSign > 0) { - setMin(0); - } - } - - if (min === max) { - setMax(max + 1); - - if (!beginAtZero) { - setMin(min - 1); - } - } - me.min = min; - me.max = max; - } - - getTickLimit() { - const me = this; - const tickOpts = me.options.ticks; - // eslint-disable-next-line prefer-const - let {maxTicksLimit, stepSize} = tickOpts; - let maxTicks; - - if (stepSize) { - maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; - } else { - maxTicks = me.computeTickLimit(); - maxTicksLimit = maxTicksLimit || 11; - } - - if (maxTicksLimit) { - maxTicks = Math.min(maxTicksLimit, maxTicks); - } - - return maxTicks; - } - - /** + constructor(cfg) { + super(cfg); + + /** @type {number} */ + this.start = undefined; + /** @type {number} */ + this.end = undefined; + /** @type {number} */ + this._startValue = undefined; + /** @type {number} */ + this._endValue = undefined; + this._valueRange = 0; + } + + parse(raw, index) { // eslint-disable-line no-unused-vars + if (isNullOrUndef(raw)) { + return NaN; + } + if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) { + return NaN; + } + + return +raw; + } + + handleTickRangeOptions() { + const me = this; + const {beginAtZero, stacked} = me.options; + const {minDefined, maxDefined} = me.getUserBounds(); + let {min, max} = me; + + const setMin = v => (min = minDefined ? min : v); + const setMax = v => (max = maxDefined ? max : v); + + if (beginAtZero || stacked) { + const minSign = sign(min); + const maxSign = sign(max); + + if (minSign < 0 && maxSign < 0) { + setMax(0); + } else if (minSign > 0 && maxSign > 0) { + setMin(0); + } + } + + if (min === max) { + setMax(max + 1); + + if (!beginAtZero) { + setMin(min - 1); + } + } + me.min = min; + me.max = max; + } + + getTickLimit() { + const me = this; + const tickOpts = me.options.ticks; + // eslint-disable-next-line prefer-const + let {maxTicksLimit, stepSize} = tickOpts; + let maxTicks; + + if (stepSize) { + maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; + } else { + maxTicks = me.computeTickLimit(); + maxTicksLimit = maxTicksLimit || 11; + } + + if (maxTicksLimit) { + maxTicks = Math.min(maxTicksLimit, maxTicks); + } + + return maxTicks; + } + + /** * @protected */ - computeTickLimit() { - return Number.POSITIVE_INFINITY; - } - - buildTicks() { - const me = this; - const opts = me.options; - const tickOpts = opts.ticks; - - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph. Make sure we always have at least 2 ticks - let maxTicks = me.getTickLimit(); - maxTicks = Math.max(2, maxTicks); - - const numericGeneratorOptions = { - maxTicks, - min: opts.min, - max: opts.max, - precision: tickOpts.precision, - stepSize: valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) - }; - const ticks = generateTicks(numericGeneratorOptions, me); - - // At this point, we need to update our max and min given the tick values, - // since we probably have expanded the range of the scale - if (opts.bounds === 'ticks') { - _setMinAndMaxByKey(ticks, me, 'value'); - } - - if (opts.reverse) { - ticks.reverse(); - - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - - return ticks; - } - - /** + computeTickLimit() { + return Number.POSITIVE_INFINITY; + } + + buildTicks() { + const me = this; + const opts = me.options; + const tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + let maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + const numericGeneratorOptions = { + maxTicks, + min: opts.min, + max: opts.max, + precision: tickOpts.precision, + stepSize: valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + const ticks = generateTicks(numericGeneratorOptions, me); + + // At this point, we need to update our max and min given the tick values, + // since we probably have expanded the range of the scale + if (opts.bounds === 'ticks') { + _setMinAndMaxByKey(ticks, me, 'value'); + } + + if (opts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + + return ticks; + } + + /** * @protected */ - configure() { - const me = this; - const ticks = me.ticks; - let start = me.min; - let end = me.max; - - super.configure(); - - if (me.options.offset && ticks.length) { - const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; - start -= offset; - end += offset; - } - me._startValue = start; - me._endValue = end; - me._valueRange = end - start; - } - - getLabelForValue(value) { - return formatNumber(value, this.options.locale); - } + configure() { + const me = this; + const ticks = me.ticks; + let start = me.min; + let end = me.max; + + super.configure(); + + if (me.options.offset && ticks.length) { + const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; + start -= offset; + end += offset; + } + me._startValue = start; + me._endValue = end; + me._valueRange = end - start; + } + + getLabelForValue(value) { + return formatNumber(value, this.options.locale); + } } diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 949d67a4ea7..5dccaf7926c 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -6,8 +6,8 @@ import Ticks from '../core/core.ticks'; import {formatNumber} from '../core/core.intl'; function isMajor(tickVal) { - const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal)))); - return remain === 1; + const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal)))); + return remain === 1; } /** @@ -17,169 +17,169 @@ function isMajor(tickVal) { * @returns {object[]} array of tick objects */ function generateTicks(generationOptions, dataRange) { - const endExp = Math.floor(log10(dataRange.max)); - const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); - const ticks = []; - let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); - let exp = Math.floor(log10(tickVal)); - let significand = Math.floor(tickVal / Math.pow(10, exp)); - let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; - - do { - ticks.push({value: tickVal, major: isMajor(tickVal)}); - - ++significand; - if (significand === 10) { - significand = 1; - ++exp; - precision = exp >= 0 ? 1 : precision; - } - - tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; - } while (exp < endExp || (exp === endExp && significand < endSignificand)); - - const lastTick = finiteOrDefault(generationOptions.max, tickVal); - ticks.push({value: lastTick, major: isMajor(tickVal)}); - - return ticks; + const endExp = Math.floor(log10(dataRange.max)); + const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + const ticks = []; + let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); + let exp = Math.floor(log10(tickVal)); + let significand = Math.floor(tickVal / Math.pow(10, exp)); + let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + + do { + ticks.push({value: tickVal, major: isMajor(tickVal)}); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + precision = exp >= 0 ? 1 : precision; + } + + tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + const lastTick = finiteOrDefault(generationOptions.max, tickVal); + ticks.push({value: lastTick, major: isMajor(tickVal)}); + + return ticks; } export default class LogarithmicScale extends Scale { - constructor(cfg) { - super(cfg); - - /** @type {number} */ - this.start = undefined; - /** @type {number} */ - this.end = undefined; - /** @type {number} */ - this._startValue = undefined; - this._valueRange = 0; - } - - parse(raw, index) { - const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]); - if (value === 0) { - this._zero = true; - return undefined; - } - return isFinite(value) && value > 0 ? value : NaN; - } - - determineDataLimits() { - const me = this; - const {min, max} = me.getMinMax(true); - - me.min = isFinite(min) ? Math.max(0, min) : null; - me.max = isFinite(max) ? Math.max(0, max) : null; - - if (me.options.beginAtZero) { - me._zero = true; - } - - me.handleTickRangeOptions(); - } - - handleTickRangeOptions() { - const me = this; - const {minDefined, maxDefined} = me.getUserBounds(); - let min = me.min; - let max = me.max; - - const setMin = v => (min = minDefined ? min : v); - const setMax = v => (max = maxDefined ? max : v); - const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m); - - if (min === max) { - if (min <= 0) { // includes null - setMin(1); - setMax(10); - } else { - setMin(exp(min, -1)); - setMax(exp(max, +1)); - } - } - if (min <= 0) { - setMin(exp(max, -1)); - } - if (max <= 0) { - setMax(exp(min, +1)); - } - // if data has `0` in it or `beginAtZero` is true, min (non zero) value is at bottom - // of scale, and it does not equal suggestedMin, lower the min bound by one exp. - if (me._zero && me.min !== me._suggestedMin && min === exp(me.min, 0)) { - setMin(exp(min, -1)); - } - me.min = min; - me.max = max; - } - - buildTicks() { - const me = this; - const opts = me.options; - - const generationOptions = { - min: me._userMin, - max: me._userMax - }; - const ticks = generateTicks(generationOptions, me); - - // At this point, we need to update our max and min given the tick values, - // since we probably have expanded the range of the scale - if (opts.bounds === 'ticks') { - _setMinAndMaxByKey(ticks, me, 'value'); - } - - if (opts.reverse) { - ticks.reverse(); - - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - - return ticks; - } - - /** + constructor(cfg) { + super(cfg); + + /** @type {number} */ + this.start = undefined; + /** @type {number} */ + this.end = undefined; + /** @type {number} */ + this._startValue = undefined; + this._valueRange = 0; + } + + parse(raw, index) { + const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]); + if (value === 0) { + this._zero = true; + return undefined; + } + return isFinite(value) && value > 0 ? value : NaN; + } + + determineDataLimits() { + const me = this; + const {min, max} = me.getMinMax(true); + + me.min = isFinite(min) ? Math.max(0, min) : null; + me.max = isFinite(max) ? Math.max(0, max) : null; + + if (me.options.beginAtZero) { + me._zero = true; + } + + me.handleTickRangeOptions(); + } + + handleTickRangeOptions() { + const me = this; + const {minDefined, maxDefined} = me.getUserBounds(); + let min = me.min; + let max = me.max; + + const setMin = v => (min = minDefined ? min : v); + const setMax = v => (max = maxDefined ? max : v); + const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m); + + if (min === max) { + if (min <= 0) { // includes null + setMin(1); + setMax(10); + } else { + setMin(exp(min, -1)); + setMax(exp(max, +1)); + } + } + if (min <= 0) { + setMin(exp(max, -1)); + } + if (max <= 0) { + setMax(exp(min, +1)); + } + // if data has `0` in it or `beginAtZero` is true, min (non zero) value is at bottom + // of scale, and it does not equal suggestedMin, lower the min bound by one exp. + if (me._zero && me.min !== me._suggestedMin && min === exp(me.min, 0)) { + setMin(exp(min, -1)); + } + me.min = min; + me.max = max; + } + + buildTicks() { + const me = this; + const opts = me.options; + + const generationOptions = { + min: me._userMin, + max: me._userMax + }; + const ticks = generateTicks(generationOptions, me); + + // At this point, we need to update our max and min given the tick values, + // since we probably have expanded the range of the scale + if (opts.bounds === 'ticks') { + _setMinAndMaxByKey(ticks, me, 'value'); + } + + if (opts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + + return ticks; + } + + /** * @param {number} value * @return {string} */ - getLabelForValue(value) { - return value === undefined ? '0' : formatNumber(value, this.options.locale); - } + getLabelForValue(value) { + return value === undefined ? '0' : formatNumber(value, this.options.locale); + } - /** + /** * @protected */ - configure() { - const me = this; - const start = me.min; - - super.configure(); - - me._startValue = log10(start); - me._valueRange = log10(me.max) - log10(start); - } - - getPixelForValue(value) { - const me = this; - if (value === undefined || value === 0) { - value = me.min; - } - return me.getPixelForDecimal(value === me.min - ? 0 - : (log10(value) - me._startValue) / me._valueRange); - } - - getValueForPixel(pixel) { - const me = this; - const decimal = me.getDecimalForPixel(pixel); - return Math.pow(10, me._startValue + decimal * me._valueRange); - } + configure() { + const me = this; + const start = me.min; + + super.configure(); + + me._startValue = log10(start); + me._valueRange = log10(me.max) - log10(start); + } + + getPixelForValue(value) { + const me = this; + if (value === undefined || value === 0) { + value = me.min; + } + return me.getPixelForDecimal(value === me.min + ? 0 + : (log10(value) - me._startValue) / me._valueRange); + } + + getValueForPixel(pixel) { + const me = this; + const decimal = me.getDecimalForPixel(pixel); + return Math.pow(10, me._startValue + decimal * me._valueRange); + } } LogarithmicScale.id = 'logarithmic'; @@ -188,10 +188,10 @@ LogarithmicScale.id = 'logarithmic'; * @type {any} */ LogarithmicScale.defaults = { - ticks: { - callback: Ticks.formatters.logarithmic, - major: { - enabled: true - } - } + ticks: { + callback: Ticks.formatters.logarithmic, + major: { + enabled: true + } + } }; diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 92411cfbf8e..45e938e2938 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -7,45 +7,45 @@ import {valueOrDefault, isArray, isFinite, callback as callCallback, isNullOrUnd import {toFont, resolve} from '../helpers/helpers.options'; function getTickBackdropHeight(opts) { - const tickOpts = opts.ticks; + const tickOpts = opts.ticks; - if (tickOpts.display && opts.display) { - return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + tickOpts.backdropPaddingY * 2; - } - return 0; + if (tickOpts.display && opts.display) { + return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + tickOpts.backdropPaddingY * 2; + } + return 0; } function measureLabelSize(ctx, lineHeight, label) { - if (isArray(label)) { - return { - w: _longestText(ctx, ctx.font, label), - h: label.length * lineHeight - }; - } - - return { - w: ctx.measureText(label).width, - h: lineHeight - }; + if (isArray(label)) { + return { + w: _longestText(ctx, ctx.font, label), + h: label.length * lineHeight + }; + } + + return { + w: ctx.measureText(label).width, + h: lineHeight + }; } function determineLimits(angle, pos, size, min, max) { - if (angle === min || angle === max) { - return { - start: pos - (size / 2), - end: pos + (size / 2) - }; - } else if (angle < min || angle > max) { - return { - start: pos - size, - end: pos - }; - } - - return { - start: pos, - end: pos + size - }; + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size, + end: pos + }; + } + + return { + start: pos, + end: pos + size + }; } /** @@ -53,455 +53,455 @@ function determineLimits(angle, pos, size, min, max) { */ function fitWithPointLabels(scale) { - // Right, this is really confusing and there is a lot of maths going on here - // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 - // - // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif - // - // Solution: - // - // We assume the radius of the polygon is half the size of the canvas at first - // at each index we check if the text overlaps. - // - // Where it does, we store that angle and that index. - // - // After finding the largest index and angle we calculate how much we need to remove - // from the shape radius to move the point inwards by that x. - // - // We average the left and right distances to get the maximum shape radius that can fit in the box - // along with labels. - // - // Once we have that, we can find the centre point for the chart, by taking the x text protrusion - // on each side, removing that from the size, halving it and adding the left x protrusion width. - // - // This will mean we have a shape fitted to the canvas, as large as it can be with the labels - // and position it in the most space efficient manner - // - // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif - - // 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 - const furthestLimits = { - l: 0, - r: scale.width, - t: 0, - b: scale.height - scale.paddingTop - }; - const furthestAngles = {}; - let i, textSize, pointPosition; - - scale._pointLabelSizes = []; - - const valueCount = scale.chart.data.labels.length; - for (i = 0; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); - - const context = scale.getContext(i); - const plFont = toFont(resolve([scale.options.pointLabels.font], context, i), scale.chart.options.font); - scale.ctx.font = plFont.string; - textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); - scale._pointLabelSizes[i] = textSize; - - // Add quarter circle to make degree 0 mean top of circle - const angleRadians = scale.getIndexAngle(i); - const angle = toDegrees(angleRadians); - const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); - const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); - - if (hLimits.start < furthestLimits.l) { - furthestLimits.l = hLimits.start; - furthestAngles.l = angleRadians; - } - - if (hLimits.end > furthestLimits.r) { - furthestLimits.r = hLimits.end; - furthestAngles.r = angleRadians; - } - - if (vLimits.start < furthestLimits.t) { - furthestLimits.t = vLimits.start; - furthestAngles.t = angleRadians; - } - - if (vLimits.end > furthestLimits.b) { - furthestLimits.b = vLimits.end; - furthestAngles.b = angleRadians; - } - } - - scale._setReductions(scale.drawingArea, furthestLimits, furthestAngles); + // Right, this is really confusing and there is a lot of maths going on here + // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + // + // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + // + // Solution: + // + // We assume the radius of the polygon is half the size of the canvas at first + // at each index we check if the text overlaps. + // + // Where it does, we store that angle and that index. + // + // After finding the largest index and angle we calculate how much we need to remove + // from the shape radius to move the point inwards by that x. + // + // We average the left and right distances to get the maximum shape radius that can fit in the box + // along with labels. + // + // Once we have that, we can find the centre point for the chart, by taking the x text protrusion + // on each side, removing that from the size, halving it and adding the left x protrusion width. + // + // This will mean we have a shape fitted to the canvas, as large as it can be with the labels + // and position it in the most space efficient manner + // + // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + + // 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 + const furthestLimits = { + l: 0, + r: scale.width, + t: 0, + b: scale.height - scale.paddingTop + }; + const furthestAngles = {}; + let i, textSize, pointPosition; + + scale._pointLabelSizes = []; + + const valueCount = scale.chart.data.labels.length; + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); + + const context = scale.getContext(i); + const plFont = toFont(resolve([scale.options.pointLabels.font], context, i), scale.chart.options.font); + scale.ctx.font = plFont.string; + textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + const angleRadians = scale.getIndexAngle(i); + const angle = toDegrees(angleRadians); + const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale._setReductions(scale.drawingArea, furthestLimits, furthestAngles); } function getTextAlignForAngle(angle) { - if (angle === 0 || angle === 180) { - return 'center'; - } else if (angle < 180) { - return 'left'; - } + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } - return 'right'; + return 'right'; } function adjustPointPositionForLabelHeight(angle, textSize, position) { - if (angle === 90 || angle === 270) { - position.y -= (textSize.h / 2); - } else if (angle > 270 || angle < 90) { - position.y -= textSize.h; - } + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } } function drawPointLabels(scale) { - const ctx = scale.ctx; - const opts = scale.options; - const pointLabelOpts = opts.pointLabels; - const tickBackdropHeight = getTickBackdropHeight(opts); - const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); - - ctx.save(); - - ctx.textBaseline = 'middle'; - - for (let i = scale.chart.data.labels.length - 1; i >= 0; i--) { - // Extra pixels out for some label spacing - const extra = (i === 0 ? tickBackdropHeight / 2 : 0); - const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); - - const context = scale.getContext(i); - const plFont = toFont(resolve([pointLabelOpts.font], context, i), scale.chart.options.font); - const angle = toDegrees(scale.getIndexAngle(i)); - adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); - renderText( - ctx, - scale.pointLabels[i], - pointLabelPosition.x, - pointLabelPosition.y + (plFont.lineHeight / 2), - plFont, - { - color: resolve([pointLabelOpts.color], context, i), - textAlign: getTextAlignForAngle(angle), - } - ); - } - ctx.restore(); + const ctx = scale.ctx; + const opts = scale.options; + const pointLabelOpts = opts.pointLabels; + const tickBackdropHeight = getTickBackdropHeight(opts); + const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + + ctx.save(); + + ctx.textBaseline = 'middle'; + + for (let i = scale.chart.data.labels.length - 1; i >= 0; i--) { + // Extra pixels out for some label spacing + const extra = (i === 0 ? tickBackdropHeight / 2 : 0); + const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); + + const context = scale.getContext(i); + const plFont = toFont(resolve([pointLabelOpts.font], context, i), scale.chart.options.font); + const angle = toDegrees(scale.getIndexAngle(i)); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + renderText( + ctx, + scale.pointLabels[i], + pointLabelPosition.x, + pointLabelPosition.y + (plFont.lineHeight / 2), + plFont, + { + color: resolve([pointLabelOpts.color], context, i), + textAlign: getTextAlignForAngle(angle), + } + ); + } + ctx.restore(); } function drawRadiusLine(scale, gridLineOpts, radius, index) { - const ctx = scale.ctx; - const circular = gridLineOpts.circular; - const valueCount = scale.chart.data.labels.length; - - const context = scale.getContext(index); - const lineColor = resolve([gridLineOpts.color], context, index - 1); - const lineWidth = resolve([gridLineOpts.lineWidth], context, index - 1); - let pointPosition; - - if ((!circular && !valueCount) || !lineColor || !lineWidth || radius < 0) { - return; - } - - ctx.save(); - ctx.strokeStyle = lineColor; - ctx.lineWidth = lineWidth; - if (ctx.setLineDash) { - ctx.setLineDash(resolve([gridLineOpts.borderDash, []], context)); - ctx.lineDashOffset = resolve([gridLineOpts.borderDashOffset], context, index - 1); - } - - ctx.beginPath(); - if (circular) { - // Draw circular arcs between the points - ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU); - } else { - // Draw straight lines connecting each index - pointPosition = scale.getPointPosition(0, radius); - ctx.moveTo(pointPosition.x, pointPosition.y); - - for (let i = 1; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, radius); - ctx.lineTo(pointPosition.x, pointPosition.y); - } - } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); + const ctx = scale.ctx; + const circular = gridLineOpts.circular; + const valueCount = scale.chart.data.labels.length; + + const context = scale.getContext(index); + const lineColor = resolve([gridLineOpts.color], context, index - 1); + const lineWidth = resolve([gridLineOpts.lineWidth], context, index - 1); + let pointPosition; + + if ((!circular && !valueCount) || !lineColor || !lineWidth || radius < 0) { + return; + } + + ctx.save(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + if (ctx.setLineDash) { + ctx.setLineDash(resolve([gridLineOpts.borderDash, []], context)); + ctx.lineDashOffset = resolve([gridLineOpts.borderDashOffset], context, index - 1); + } + + ctx.beginPath(); + if (circular) { + // Draw circular arcs between the points + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU); + } else { + // Draw straight lines connecting each index + pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (let i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); } function numberOrZero(param) { - return isNumber(param) ? param : 0; + return isNumber(param) ? param : 0; } export default class RadialLinearScale extends LinearScaleBase { - constructor(cfg) { - super(cfg); - - /** @type {number} */ - this.xCenter = undefined; - /** @type {number} */ - this.yCenter = undefined; - /** @type {number} */ - this.drawingArea = undefined; - /** @type {string[]} */ - this.pointLabels = []; - } - - init(options) { - super.init(options); - this.axis = 'r'; - } - - setDimensions() { - const me = this; - - // Set the unconstrained dimension before label rotation - me.width = me.maxWidth; - me.height = me.maxHeight; - me.paddingTop = getTickBackdropHeight(me.options) / 2; - me.xCenter = Math.floor(me.width / 2); - me.yCenter = Math.floor((me.height - me.paddingTop) / 2); - me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; - } - - determineDataLimits() { - const me = this; - const {min, max} = me.getMinMax(false); - - me.min = isFinite(min) && !isNaN(min) ? min : 0; - me.max = isFinite(max) && !isNaN(max) ? max : 0; - - // Common base implementation to handle min, max, beginAtZero - me.handleTickRangeOptions(); - } - - /** + constructor(cfg) { + super(cfg); + + /** @type {number} */ + this.xCenter = undefined; + /** @type {number} */ + this.yCenter = undefined; + /** @type {number} */ + this.drawingArea = undefined; + /** @type {string[]} */ + this.pointLabels = []; + } + + init(options) { + super.init(options); + this.axis = 'r'; + } + + setDimensions() { + const me = this; + + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.paddingTop = getTickBackdropHeight(me.options) / 2; + me.xCenter = Math.floor(me.width / 2); + me.yCenter = Math.floor((me.height - me.paddingTop) / 2); + me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; + } + + determineDataLimits() { + const me = this; + const {min, max} = me.getMinMax(false); + + me.min = isFinite(min) && !isNaN(min) ? min : 0; + me.max = isFinite(max) && !isNaN(max) ? max : 0; + + // Common base implementation to handle min, max, beginAtZero + me.handleTickRangeOptions(); + } + + /** * Returns the maximum number of ticks based on the scale dimension * @protected */ - computeTickLimit() { - return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); - } - - generateTickLabels(ticks) { - const me = this; - - LinearScaleBase.prototype.generateTickLabels.call(me, ticks); - - // Point labels - me.pointLabels = me.chart.data.labels.map((value, index) => { - const label = callCallback(me.options.pointLabels.callback, [value, index], me); - return label || label === 0 ? label : ''; - }); - } - - fit() { - const me = this; - const opts = me.options; - - if (opts.display && opts.pointLabels.display) { - fitWithPointLabels(me); - } else { - me.setCenterPoint(0, 0, 0, 0); - } - } - - /** + computeTickLimit() { + return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); + } + + generateTickLabels(ticks) { + const me = this; + + LinearScaleBase.prototype.generateTickLabels.call(me, ticks); + + // Point labels + me.pointLabels = me.chart.data.labels.map((value, index) => { + const label = callCallback(me.options.pointLabels.callback, [value, index], me); + return label || label === 0 ? label : ''; + }); + } + + fit() { + const me = this; + const opts = me.options; + + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(me); + } else { + me.setCenterPoint(0, 0, 0, 0); + } + } + + /** * Set radius reductions and determine new radius and center point * @private */ - _setReductions(largestPossibleRadius, furthestLimits, furthestAngles) { - const me = this; - let radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); - let radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); - let radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); - let radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); - - radiusReductionLeft = numberOrZero(radiusReductionLeft); - radiusReductionRight = numberOrZero(radiusReductionRight); - radiusReductionTop = numberOrZero(radiusReductionTop); - radiusReductionBottom = numberOrZero(radiusReductionBottom); - - me.drawingArea = Math.min( - Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), - Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); - me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); - } - - setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) { - const me = this; - const maxRight = me.width - rightMovement - me.drawingArea; - const maxLeft = leftMovement + me.drawingArea; - const maxTop = topMovement + me.drawingArea; - const maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; - - me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); - } - - getIndexAngle(index) { - const chart = this.chart; - const angleMultiplier = TAU / chart.data.labels.length; - const options = chart.options || {}; - const startAngle = options.startAngle || 0; - - return _normalizeAngle(index * angleMultiplier + toRadians(startAngle)); - } - - getDistanceFromCenterForValue(value) { - const me = this; - - if (isNullOrUndef(value)) { - return NaN; - } - - // Take into account half font size + the yPadding of the top value - const scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.reverse) { - return (me.max - value) * scalingFactor; - } - return (value - me.min) * scalingFactor; - } - - getValueForDistanceFromCenter(distance) { - if (isNullOrUndef(distance)) { - return NaN; - } - - const me = this; - const scaledDistance = distance / (me.drawingArea / (me.max - me.min)); - return me.options.reverse ? me.max - scaledDistance : me.min + scaledDistance; - } - - getPointPosition(index, distanceFromCenter) { - const me = this; - const angle = me.getIndexAngle(index) - HALF_PI; - return { - x: Math.cos(angle) * distanceFromCenter + me.xCenter, - y: Math.sin(angle) * distanceFromCenter + me.yCenter, - angle - }; - } - - getPointPositionForValue(index, value) { - return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); - } - - getBasePosition(index) { - return this.getPointPositionForValue(index || 0, this.getBaseValue()); - } - - /** + _setReductions(largestPossibleRadius, furthestLimits, furthestAngles) { + const me = this; + let radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + let radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + let radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + let radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + } + + setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) { + const me = this; + const maxRight = me.width - rightMovement - me.drawingArea; + const maxLeft = leftMovement + me.drawingArea; + const maxTop = topMovement + me.drawingArea; + const maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; + + me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); + } + + getIndexAngle(index) { + const chart = this.chart; + const angleMultiplier = TAU / chart.data.labels.length; + const options = chart.options || {}; + const startAngle = options.startAngle || 0; + + return _normalizeAngle(index * angleMultiplier + toRadians(startAngle)); + } + + getDistanceFromCenterForValue(value) { + const me = this; + + if (isNullOrUndef(value)) { + return NaN; + } + + // Take into account half font size + the yPadding of the top value + const scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + } + + getValueForDistanceFromCenter(distance) { + if (isNullOrUndef(distance)) { + return NaN; + } + + const me = this; + const scaledDistance = distance / (me.drawingArea / (me.max - me.min)); + return me.options.reverse ? me.max - scaledDistance : me.min + scaledDistance; + } + + getPointPosition(index, distanceFromCenter) { + const me = this; + const angle = me.getIndexAngle(index) - HALF_PI; + return { + x: Math.cos(angle) * distanceFromCenter + me.xCenter, + y: Math.sin(angle) * distanceFromCenter + me.yCenter, + angle + }; + } + + getPointPositionForValue(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + } + + getBasePosition(index) { + return this.getPointPositionForValue(index || 0, this.getBaseValue()); + } + + /** * @protected */ - drawGrid() { - const me = this; - const ctx = me.ctx; - const opts = me.options; - const gridLineOpts = opts.gridLines; - const angleLineOpts = opts.angleLines; - let i, offset, position; - - if (opts.pointLabels.display) { - drawPointLabels(me); - } - - if (gridLineOpts.display) { - me.ticks.forEach((tick, index) => { - if (index !== 0) { - offset = me.getDistanceFromCenterForValue(me.ticks[index].value); - drawRadiusLine(me, gridLineOpts, offset, index); - } - }); - } - - if (angleLineOpts.display) { - ctx.save(); - - for (i = me.chart.data.labels.length - 1; i >= 0; i--) { - const context = me.getContext(i); - const lineWidth = resolve([angleLineOpts.lineWidth, gridLineOpts.lineWidth], context, i); - const color = resolve([angleLineOpts.color, gridLineOpts.color], context, i); - - if (!lineWidth || !color) { - continue; - } - - ctx.lineWidth = lineWidth; - ctx.strokeStyle = color; - - if (ctx.setLineDash) { - ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []], context)); - ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0], context, i); - } - - offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); - position = me.getPointPosition(i, offset); - ctx.beginPath(); - ctx.moveTo(me.xCenter, me.yCenter); - ctx.lineTo(position.x, position.y); - ctx.stroke(); - } - - ctx.restore(); - } - } - - /** + drawGrid() { + const me = this; + const ctx = me.ctx; + const opts = me.options; + const gridLineOpts = opts.gridLines; + const angleLineOpts = opts.angleLines; + let i, offset, position; + + if (opts.pointLabels.display) { + drawPointLabels(me); + } + + if (gridLineOpts.display) { + me.ticks.forEach((tick, index) => { + if (index !== 0) { + offset = me.getDistanceFromCenterForValue(me.ticks[index].value); + drawRadiusLine(me, gridLineOpts, offset, index); + } + }); + } + + if (angleLineOpts.display) { + ctx.save(); + + for (i = me.chart.data.labels.length - 1; i >= 0; i--) { + const context = me.getContext(i); + const lineWidth = resolve([angleLineOpts.lineWidth, gridLineOpts.lineWidth], context, i); + const color = resolve([angleLineOpts.color, gridLineOpts.color], context, i); + + if (!lineWidth || !color) { + continue; + } + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = color; + + if (ctx.setLineDash) { + ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []], context)); + ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0], context, i); + } + + offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); + position = me.getPointPosition(i, offset); + ctx.beginPath(); + ctx.moveTo(me.xCenter, me.yCenter); + ctx.lineTo(position.x, position.y); + ctx.stroke(); + } + + ctx.restore(); + } + } + + /** * @protected */ - drawLabels() { - const me = this; - const ctx = me.ctx; - const opts = me.options; - const tickOpts = opts.ticks; - - if (!tickOpts.display) { - return; - } - - const startAngle = me.getIndexAngle(0); - let offset, width; - - ctx.save(); - ctx.translate(me.xCenter, me.yCenter); - ctx.rotate(startAngle); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - me.ticks.forEach((tick, index) => { - if (index === 0 && !opts.reverse) { - return; - } - - const context = me.getContext(index); - const tickFont = me._resolveTickFontOptions(index); - offset = me.getDistanceFromCenterForValue(me.ticks[index].value); - - const showLabelBackdrop = resolve([tickOpts.showLabelBackdrop], context, index); - - if (showLabelBackdrop) { - width = ctx.measureText(tick.label).width; - ctx.fillStyle = resolve([tickOpts.backdropColor], context, index); - - ctx.fillRect( - -width / 2 - tickOpts.backdropPaddingX, - -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, - width + tickOpts.backdropPaddingX * 2, - tickFont.size + tickOpts.backdropPaddingY * 2 - ); - } - - renderText(ctx, tick.label, 0, -offset, tickFont, { - color: tickOpts.color, - }); - }); - - ctx.restore(); - } - - /** + drawLabels() { + const me = this; + const ctx = me.ctx; + const opts = me.options; + const tickOpts = opts.ticks; + + if (!tickOpts.display) { + return; + } + + const startAngle = me.getIndexAngle(0); + let offset, width; + + ctx.save(); + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + me.ticks.forEach((tick, index) => { + if (index === 0 && !opts.reverse) { + return; + } + + const context = me.getContext(index); + const tickFont = me._resolveTickFontOptions(index); + offset = me.getDistanceFromCenterForValue(me.ticks[index].value); + + const showLabelBackdrop = resolve([tickOpts.showLabelBackdrop], context, index); + + if (showLabelBackdrop) { + width = ctx.measureText(tick.label).width; + ctx.fillStyle = resolve([tickOpts.backdropColor], context, index); + + ctx.fillRect( + -width / 2 - tickOpts.backdropPaddingX, + -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, + width + tickOpts.backdropPaddingX * 2, + tickFont.size + tickOpts.backdropPaddingY * 2 + ); + } + + renderText(ctx, tick.label, 0, -offset, tickFont, { + color: tickOpts.color, + }); + }); + + ctx.restore(); + } + + /** * @protected */ - drawTitle() {} + drawTitle() {} } RadialLinearScale.id = 'radialLinear'; @@ -510,58 +510,58 @@ RadialLinearScale.id = 'radialLinear'; * @type {any} */ RadialLinearScale.defaults = { - display: true, - - // Boolean - Whether to animate scaling the chart from the centre - animate: true, - position: 'chartArea', - - angleLines: { - display: true, - lineWidth: 1, - borderDash: [], - borderDashOffset: 0.0 - }, - - gridLines: { - circular: false - }, - - // label settings - ticks: { - // Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, - - // String - The colour of the label backdrop - backdropColor: 'rgba(255,255,255,0.75)', - - // Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, - - // Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - - callback: Ticks.formatters.numeric - }, - - pointLabels: { - // Boolean - if true, show point labels - display: true, - - // Number - Point label font size in pixels - font: { - size: 10 - }, - - // Function - Used to convert point labels - callback(label) { - return label; - } - } + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: Ticks.formatters.numeric + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + font: { + size: 10 + }, + + // Function - Used to convert point labels + callback(label) { + return label; + } + } }; RadialLinearScale.defaultRoutes = { - 'angleLines.color': 'borderColor', - 'pointLabels.color': 'color', - 'ticks.color': 'color' + 'angleLines.color': 'borderColor', + 'pointLabels.color': 'color', + 'ticks.color': 'color' }; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 1a371f70ae6..b4f702d254e 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -16,15 +16,15 @@ const MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; * @type {Object} */ const INTERVALS = { - millisecond: {common: true, size: 1, steps: 1000}, - second: {common: true, size: 1000, steps: 60}, - minute: {common: true, size: 60000, steps: 60}, - hour: {common: true, size: 3600000, steps: 24}, - day: {common: true, size: 86400000, steps: 30}, - week: {common: false, size: 604800000, steps: 4}, - month: {common: true, size: 2.628e9, steps: 12}, - quarter: {common: false, size: 7.884e9, steps: 4}, - year: {common: true, size: 3.154e10} + millisecond: {common: true, size: 1, steps: 1000}, + second: {common: true, size: 1000, steps: 60}, + minute: {common: true, size: 60000, steps: 60}, + hour: {common: true, size: 3600000, steps: 24}, + day: {common: true, size: 86400000, steps: 30}, + week: {common: false, size: 604800000, steps: 4}, + month: {common: true, size: 2.628e9, steps: 12}, + quarter: {common: false, size: 7.884e9, steps: 4}, + year: {common: true, size: 3.154e10} }; /** @@ -37,7 +37,7 @@ const UNITS = /** @type Unit[] */(Object.keys(INTERVALS)); * @param {number} b */ function sorter(a, b) { - return a - b; + return a - b; } /** @@ -46,37 +46,37 @@ function sorter(a, b) { * @return {number} */ function parse(scale, input) { - if (isNullOrUndef(input)) { - return null; - } - - const adapter = scale._adapter; - const options = scale.options.time; - const {parser, round, isoWeekday} = options; - let value = input; - - if (typeof parser === 'function') { - value = parser(value); - } - - // Only parse if its not a timestamp already - if (!isFinite(value)) { - value = typeof parser === 'string' - ? adapter.parse(value, parser) - : adapter.parse(value); - } - - if (value === null) { - return value; - } - - if (round) { - value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) - ? scale._adapter.startOf(value, 'isoWeek', isoWeekday) - : scale._adapter.startOf(value, round); - } - - return +value; + if (isNullOrUndef(input)) { + return null; + } + + const adapter = scale._adapter; + const options = scale.options.time; + const {parser, round, isoWeekday} = options; + let value = input; + + if (typeof parser === 'function') { + value = parser(value); + } + + // Only parse if its not a timestamp already + if (!isFinite(value)) { + value = typeof parser === 'string' + ? adapter.parse(value, parser) + : adapter.parse(value); + } + + if (value === null) { + return value; + } + + if (round) { + value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) + ? scale._adapter.startOf(value, 'isoWeek', isoWeekday) + : scale._adapter.startOf(value, round); + } + + return +value; } /** @@ -88,18 +88,18 @@ function parse(scale, input) { * @return {object} */ function determineUnitForAutoTicks(minUnit, min, max, capacity) { - const ilen = UNITS.length; + const ilen = UNITS.length; - for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { - const interval = INTERVALS[UNITS[i]]; - const factor = interval.steps ? interval.steps : MAX_INTEGER; + for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + const interval = INTERVALS[UNITS[i]]; + const factor = interval.steps ? interval.steps : MAX_INTEGER; - if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { - return UNITS[i]; - } - } + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } - return UNITS[ilen - 1]; + return UNITS[ilen - 1]; } /** @@ -112,14 +112,14 @@ function determineUnitForAutoTicks(minUnit, min, max, capacity) { * @return {Unit} */ function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { - for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { - const unit = UNITS[i]; - if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { - return unit; - } - } - - return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; + for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { + const unit = UNITS[i]; + if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; } /** @@ -127,11 +127,11 @@ function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { * @return {object} */ function determineMajorUnit(unit) { - for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { - if (INTERVALS[UNITS[i]].common) { - return UNITS[i]; - } - } + for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } } /** @@ -140,13 +140,13 @@ function determineMajorUnit(unit) { * @param {number[]} [timestamps] - if defined, snap to these timestamps */ function addTick(ticks, time, timestamps) { - if (!timestamps) { - ticks[time] = true; - } else if (timestamps.length) { - const {lo, hi} = _lookup(timestamps, time); - const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi]; - ticks[timestamp] = true; - } + if (!timestamps) { + ticks[time] = true; + } else if (timestamps.length) { + const {lo, hi} = _lookup(timestamps, time); + const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi]; + ticks[timestamp] = true; + } } /** @@ -157,18 +157,18 @@ function addTick(ticks, time, timestamps) { * @return {object[]} */ function setMajorTicks(scale, ticks, map, majorUnit) { - const adapter = scale._adapter; - const first = +adapter.startOf(ticks[0].value, majorUnit); - const last = ticks[ticks.length - 1].value; - let major, index; - - for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { - index = map[major]; - if (index >= 0) { - ticks[index].major = true; - } - } - return ticks; + const adapter = scale._adapter; + const first = +adapter.startOf(ticks[0].value, majorUnit); + const last = ticks[ticks.length - 1].value; + let major, index; + + for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { + index = map[major]; + if (index >= 0) { + ticks[index].major = true; + } + } + return ticks; } /** @@ -178,179 +178,179 @@ function setMajorTicks(scale, ticks, map, majorUnit) { * @return {object[]} */ function ticksFromTimestamps(scale, values, majorUnit) { - const ticks = []; - /** @type {Object} */ - const map = {}; - const ilen = values.length; - let i, value; - - for (i = 0; i < ilen; ++i) { - value = values[i]; - map[value] = i; - - ticks.push({ - value, - major: false - }); - } - - // We set the major ticks separately from the above loop because calling startOf for every tick - // is expensive when there is a large number of ticks - return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); + const ticks = []; + /** @type {Object} */ + const map = {}; + const ilen = values.length; + let i, value; + + for (i = 0; i < ilen; ++i) { + value = values[i]; + map[value] = i; + + ticks.push({ + value, + major: false + }); + } + + // We set the major ticks separately from the above loop because calling startOf for every tick + // is expensive when there is a large number of ticks + return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); } export default class TimeScale extends Scale { - /** + /** * @param {object} props */ - constructor(props) { - super(props); - - /** @type {{data: number[], labels: number[], all: number[]}} */ - this._cache = { - data: [], - labels: [], - all: [] - }; - - /** @type {Unit} */ - this._unit = 'day'; - /** @type {Unit=} */ - this._majorUnit = undefined; - this._offsets = {}; - this._normalized = false; - } - - init(scaleOpts, opts) { - const time = scaleOpts.time || (scaleOpts.time = {}); - const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date); - - // Backward compatibility: before introducing adapter, `displayFormats` was - // supposed to contain *all* unit/string pairs but this can't be resolved - // when loading the scale (adapters are loaded afterward), so let's populate - // missing formats on update - mergeIf(time.displayFormats, adapter.formats()); - - super.init(scaleOpts); - - this._normalized = opts.normalized; - } - - /** + constructor(props) { + super(props); + + /** @type {{data: number[], labels: number[], all: number[]}} */ + this._cache = { + data: [], + labels: [], + all: [] + }; + + /** @type {Unit} */ + this._unit = 'day'; + /** @type {Unit=} */ + this._majorUnit = undefined; + this._offsets = {}; + this._normalized = false; + } + + init(scaleOpts, opts) { + const time = scaleOpts.time || (scaleOpts.time = {}); + const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date); + + // Backward compatibility: before introducing adapter, `displayFormats` was + // supposed to contain *all* unit/string pairs but this can't be resolved + // when loading the scale (adapters are loaded afterward), so let's populate + // missing formats on update + mergeIf(time.displayFormats, adapter.formats()); + + super.init(scaleOpts); + + this._normalized = opts.normalized; + } + + /** * @param {*} raw * @param {number?} [index] * @return {number} */ - parse(raw, index) { // eslint-disable-line no-unused-vars - if (raw === undefined) { - return NaN; - } - return parse(this, raw); - } - - beforeLayout() { - super.beforeLayout(); - this._cache = { - data: [], - labels: [], - all: [] - }; - } - - determineDataLimits() { - const me = this; - const options = me.options; - const adapter = me._adapter; - const unit = options.time.unit || 'day'; - // eslint-disable-next-line prefer-const - let {min, max, minDefined, maxDefined} = me.getUserBounds(); - - /** + parse(raw, index) { // eslint-disable-line no-unused-vars + if (raw === undefined) { + return NaN; + } + return parse(this, raw); + } + + beforeLayout() { + super.beforeLayout(); + this._cache = { + data: [], + labels: [], + all: [] + }; + } + + determineDataLimits() { + const me = this; + const options = me.options; + const adapter = me._adapter; + const unit = options.time.unit || 'day'; + // eslint-disable-next-line prefer-const + let {min, max, minDefined, maxDefined} = me.getUserBounds(); + + /** * @param {object} bounds */ - function _applyBounds(bounds) { - if (!minDefined && !isNaN(bounds.min)) { - min = Math.min(min, bounds.min); - } - if (!maxDefined && !isNaN(bounds.max)) { - max = Math.max(max, bounds.max); - } - } - - // If we have user provided `min` and `max` labels / data bounds can be ignored - if (!minDefined || !maxDefined) { - // Labels are always considered, when user did not force bounds - _applyBounds(me._getLabelBounds()); - - // If `bounds` is `'ticks'` and `ticks.source` is `'labels'`, - // data bounds are ignored (and don't need to be determined) - if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') { - _applyBounds(me.getMinMax(false)); - } - } - - min = isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit); - max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1; - - // Make sure that max is strictly higher than min (required by the timeseries lookup table) - me.min = Math.min(min, max); - me.max = Math.max(min + 1, max); - } - - /** + function _applyBounds(bounds) { + if (!minDefined && !isNaN(bounds.min)) { + min = Math.min(min, bounds.min); + } + if (!maxDefined && !isNaN(bounds.max)) { + max = Math.max(max, bounds.max); + } + } + + // If we have user provided `min` and `max` labels / data bounds can be ignored + if (!minDefined || !maxDefined) { + // Labels are always considered, when user did not force bounds + _applyBounds(me._getLabelBounds()); + + // If `bounds` is `'ticks'` and `ticks.source` is `'labels'`, + // data bounds are ignored (and don't need to be determined) + if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') { + _applyBounds(me.getMinMax(false)); + } + } + + min = isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit); + max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1; + + // Make sure that max is strictly higher than min (required by the timeseries lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + } + + /** * @private */ - _getLabelBounds() { - const arr = this.getLabelTimestamps(); - let min = Number.POSITIVE_INFINITY; - let max = Number.NEGATIVE_INFINITY; - - if (arr.length) { - min = arr[0]; - max = arr[arr.length - 1]; - } - return {min, max}; - } - - /** + _getLabelBounds() { + const arr = this.getLabelTimestamps(); + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + + if (arr.length) { + min = arr[0]; + max = arr[arr.length - 1]; + } + return {min, max}; + } + + /** * @return {object[]} */ - buildTicks() { - const me = this; - const options = me.options; - const timeOpts = options.time; - const tickOpts = options.ticks; - const timestamps = tickOpts.source === 'labels' ? me.getLabelTimestamps() : me._generate(); - - if (options.bounds === 'ticks' && timestamps.length) { - me.min = me._userMin || timestamps[0]; - me.max = me._userMax || timestamps[timestamps.length - 1]; - } - - const min = me.min; - const max = me.max; - - const ticks = _filterBetween(timestamps, min, max); - - // PRIVATE - // determineUnitForFormatting relies on the number of ticks so we don't use it when - // autoSkip is enabled because we don't yet know what the final number of ticks will be - me._unit = timeOpts.unit || (tickOpts.autoSkip - ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, me._getLabelCapacity(min)) - : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); - me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined - : determineMajorUnit(me._unit); - me.initOffsets(timestamps); - - if (options.reverse) { - ticks.reverse(); - } - - return ticksFromTimestamps(me, ticks, me._majorUnit); - } - - /** + buildTicks() { + const me = this; + const options = me.options; + const timeOpts = options.time; + const tickOpts = options.ticks; + const timestamps = tickOpts.source === 'labels' ? me.getLabelTimestamps() : me._generate(); + + if (options.bounds === 'ticks' && timestamps.length) { + me.min = me._userMin || timestamps[0]; + me.max = me._userMax || timestamps[timestamps.length - 1]; + } + + const min = me.min; + const max = me.max; + + const ticks = _filterBetween(timestamps, min, max); + + // PRIVATE + // determineUnitForFormatting relies on the number of ticks so we don't use it when + // autoSkip is enabled because we don't yet know what the final number of ticks will be + me._unit = timeOpts.unit || (tickOpts.autoSkip + ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, me._getLabelCapacity(min)) + : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); + me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined + : determineMajorUnit(me._unit); + me.initOffsets(timestamps); + + if (options.reverse) { + ticks.reverse(); + } + + return ticksFromTimestamps(me, ticks, me._majorUnit); + } + + /** * Returns the start and end offsets from edges in the form of {start, end} * where each value is a relative width to the scale and ranges between 0 and 1. * They add extra margins on the both sides by scaling down the original scale. @@ -359,95 +359,95 @@ export default class TimeScale extends Scale { * @return {object} * @protected */ - initOffsets(timestamps) { - const me = this; - let start = 0; - let end = 0; - let first, last; - - if (me.options.offset && timestamps.length) { - first = me.getDecimalForValue(timestamps[0]); - if (timestamps.length === 1) { - start = 1 - first; - } else { - start = (me.getDecimalForValue(timestamps[1]) - first) / 2; - } - last = me.getDecimalForValue(timestamps[timestamps.length - 1]); - if (timestamps.length === 1) { - end = last; - } else { - end = (last - me.getDecimalForValue(timestamps[timestamps.length - 2])) / 2; - } - } - - me._offsets = {start, end, factor: 1 / (start + 1 + end)}; - } - - /** + initOffsets(timestamps) { + const me = this; + let start = 0; + let end = 0; + let first, last; + + if (me.options.offset && timestamps.length) { + first = me.getDecimalForValue(timestamps[0]); + if (timestamps.length === 1) { + start = 1 - first; + } else { + start = (me.getDecimalForValue(timestamps[1]) - first) / 2; + } + last = me.getDecimalForValue(timestamps[timestamps.length - 1]); + if (timestamps.length === 1) { + end = last; + } else { + end = (last - me.getDecimalForValue(timestamps[timestamps.length - 2])) / 2; + } + } + + me._offsets = {start, end, factor: 1 / (start + 1 + end)}; + } + + /** * Generates a maximum of `capacity` timestamps between min and max, rounded to the * `minor` unit using the given scale time `options`. * Important: this method can return ticks outside the min and max range, it's the * responsibility of the calling code to clamp values if needed. * @private */ - _generate() { - const me = this; - const adapter = me._adapter; - const min = me.min; - const max = me.max; - const options = me.options; - const timeOpts = options.time; - // @ts-ignore - const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, me._getLabelCapacity(min)); - const stepSize = valueOrDefault(timeOpts.stepSize, 1); - const weekday = minor === 'week' ? timeOpts.isoWeekday : false; - const hasWeekday = isNumber(weekday) || weekday === true; - const ticks = {}; - let first = min; - let time; - - // For 'week' unit, handle the first day of week option - if (hasWeekday) { - first = +adapter.startOf(first, 'isoWeek', weekday); - } - - // Align first ticks on unit - first = +adapter.startOf(first, hasWeekday ? 'day' : minor); - - // Prevent browser from freezing in case user options request millions of milliseconds - if (adapter.diff(max, min, minor) > 100000 * stepSize) { - throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor); - } - - const timestamps = options.ticks.source === 'data' && me.getDataTimestamps(); - for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { - addTick(ticks, time, timestamps); - } - - if (time === max || options.bounds === 'ticks') { - addTick(ticks, time, timestamps); - } - - // @ts-ignore - return Object.keys(ticks).sort((a, b) => a - b).map(x => +x); - } - - /** + _generate() { + const me = this; + const adapter = me._adapter; + const min = me.min; + const max = me.max; + const options = me.options; + const timeOpts = options.time; + // @ts-ignore + const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, me._getLabelCapacity(min)); + const stepSize = valueOrDefault(timeOpts.stepSize, 1); + const weekday = minor === 'week' ? timeOpts.isoWeekday : false; + const hasWeekday = isNumber(weekday) || weekday === true; + const ticks = {}; + let first = min; + let time; + + // For 'week' unit, handle the first day of week option + if (hasWeekday) { + first = +adapter.startOf(first, 'isoWeek', weekday); + } + + // Align first ticks on unit + first = +adapter.startOf(first, hasWeekday ? 'day' : minor); + + // Prevent browser from freezing in case user options request millions of milliseconds + if (adapter.diff(max, min, minor) > 100000 * stepSize) { + throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor); + } + + const timestamps = options.ticks.source === 'data' && me.getDataTimestamps(); + for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { + addTick(ticks, time, timestamps); + } + + if (time === max || options.bounds === 'ticks') { + addTick(ticks, time, timestamps); + } + + // @ts-ignore + return Object.keys(ticks).sort((a, b) => a - b).map(x => +x); + } + + /** * @param {number} value * @return {string} */ - getLabelForValue(value) { - const me = this; - const adapter = me._adapter; - const timeOpts = me.options.time; - - if (timeOpts.tooltipFormat) { - return adapter.format(value, timeOpts.tooltipFormat); - } - return adapter.format(value, timeOpts.displayFormats.datetime); - } - - /** + getLabelForValue(value) { + const me = this; + const adapter = me._adapter; + const timeOpts = me.options.time; + + if (timeOpts.tooltipFormat) { + return adapter.format(value, timeOpts.tooltipFormat); + } + return adapter.format(value, timeOpts.displayFormats.datetime); + } + + /** * Function to format an individual tick mark * @param {number} time * @param {number} index @@ -456,157 +456,157 @@ export default class TimeScale extends Scale { * @return {string} * @private */ - _tickFormatFunction(time, index, ticks, format) { - const me = this; - const options = me.options; - const formats = options.time.displayFormats; - const unit = me._unit; - const majorUnit = me._majorUnit; - const minorFormat = unit && formats[unit]; - const majorFormat = majorUnit && formats[majorUnit]; - const tick = ticks[index]; - const major = majorUnit && majorFormat && tick && tick.major; - const label = me._adapter.format(time, format || (major ? majorFormat : minorFormat)); - const formatter = options.ticks.callback; - return formatter ? formatter(label, index, ticks) : label; - } - - /** + _tickFormatFunction(time, index, ticks, format) { + const me = this; + const options = me.options; + const formats = options.time.displayFormats; + const unit = me._unit; + const majorUnit = me._majorUnit; + const minorFormat = unit && formats[unit]; + const majorFormat = majorUnit && formats[majorUnit]; + const tick = ticks[index]; + const major = majorUnit && majorFormat && tick && tick.major; + const label = me._adapter.format(time, format || (major ? majorFormat : minorFormat)); + const formatter = options.ticks.callback; + return formatter ? formatter(label, index, ticks) : label; + } + + /** * @param {object[]} ticks */ - generateTickLabels(ticks) { - let i, ilen, tick; + generateTickLabels(ticks) { + let i, ilen, tick; - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - tick = ticks[i]; - tick.label = this._tickFormatFunction(tick.value, i, ticks); - } - } + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + tick = ticks[i]; + tick.label = this._tickFormatFunction(tick.value, i, ticks); + } + } - /** + /** * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC) * @return {number} */ - getDecimalForValue(value) { - const me = this; - return (value - me.min) / (me.max - me.min); - } + getDecimalForValue(value) { + const me = this; + return (value - me.min) / (me.max - me.min); + } - /** + /** * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC) * @return {number} */ - getPixelForValue(value) { - const me = this; - const offsets = me._offsets; - const pos = me.getDecimalForValue(value); - return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); - } - - /** + getPixelForValue(value) { + const me = this; + const offsets = me._offsets; + const pos = me.getDecimalForValue(value); + return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); + } + + /** * @param {number} pixel * @return {number} */ - getValueForPixel(pixel) { - const me = this; - const offsets = me._offsets; - const pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; - return me.min + pos * (me.max - me.min); - } - - /** + getValueForPixel(pixel) { + const me = this; + const offsets = me._offsets; + const pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + return me.min + pos * (me.max - me.min); + } + + /** * @param {string} label * @return {{w:number, h:number}} * @private */ - _getLabelSize(label) { - const me = this; - const ticksOpts = me.options.ticks; - const tickLabelWidth = me.ctx.measureText(label).width; - const angle = toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); - const cosRotation = Math.cos(angle); - const sinRotation = Math.sin(angle); - const tickFontSize = me._resolveTickFontOptions(0).size; - - return { - w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), - h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) - }; - } - - /** + _getLabelSize(label) { + const me = this; + const ticksOpts = me.options.ticks; + const tickLabelWidth = me.ctx.measureText(label).width; + const angle = toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); + const cosRotation = Math.cos(angle); + const sinRotation = Math.sin(angle); + const tickFontSize = me._resolveTickFontOptions(0).size; + + return { + w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), + h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) + }; + } + + /** * @param {number} exampleTime * @return {number} * @private */ - _getLabelCapacity(exampleTime) { - const me = this; - const timeOpts = me.options.time; - const displayFormats = timeOpts.displayFormats; - - // pick the longest format (milliseconds) for guestimation - const format = displayFormats[timeOpts.unit] || displayFormats.millisecond; - const exampleLabel = me._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); - const size = me._getLabelSize(exampleLabel); - // subtract 1 - if offset then there's one less label than tick - // if not offset then one half label padding is added to each end leaving room for one less label - const capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h) - 1; - return capacity > 0 ? capacity : 1; - } - - /** + _getLabelCapacity(exampleTime) { + const me = this; + const timeOpts = me.options.time; + const displayFormats = timeOpts.displayFormats; + + // pick the longest format (milliseconds) for guestimation + const format = displayFormats[timeOpts.unit] || displayFormats.millisecond; + const exampleLabel = me._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); + const size = me._getLabelSize(exampleLabel); + // subtract 1 - if offset then there's one less label than tick + // if not offset then one half label padding is added to each end leaving room for one less label + const capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h) - 1; + return capacity > 0 ? capacity : 1; + } + + /** * @protected */ - getDataTimestamps() { - const me = this; - let timestamps = me._cache.data || []; - let i, ilen; + getDataTimestamps() { + const me = this; + let timestamps = me._cache.data || []; + let i, ilen; - if (timestamps.length) { - return timestamps; - } + if (timestamps.length) { + return timestamps; + } - const metas = me.getMatchingVisibleMetas(); + const metas = me.getMatchingVisibleMetas(); - if (me._normalized && metas.length) { - return (me._cache.data = metas[0].controller.getAllParsedValues(me)); - } + if (me._normalized && metas.length) { + return (me._cache.data = metas[0].controller.getAllParsedValues(me)); + } - for (i = 0, ilen = metas.length; i < ilen; ++i) { - timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(me)); - } + for (i = 0, ilen = metas.length; i < ilen; ++i) { + timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(me)); + } - return (me._cache.data = me.normalize(timestamps)); - } + return (me._cache.data = me.normalize(timestamps)); + } - /** + /** * @protected */ - getLabelTimestamps() { - const me = this; - const timestamps = me._cache.labels || []; - let i, ilen; + getLabelTimestamps() { + const me = this; + const timestamps = me._cache.labels || []; + let i, ilen; - if (timestamps.length) { - return timestamps; - } + if (timestamps.length) { + return timestamps; + } - const labels = me.getLabels(); - for (i = 0, ilen = labels.length; i < ilen; ++i) { - timestamps.push(parse(me, labels[i])); - } + const labels = me.getLabels(); + for (i = 0, ilen = labels.length; i < ilen; ++i) { + timestamps.push(parse(me, labels[i])); + } - return (me._cache.labels = me._normalized ? timestamps : me.normalize(timestamps)); - } + return (me._cache.labels = me._normalized ? timestamps : me.normalize(timestamps)); + } - /** + /** * @param {number[]} values * @protected */ - normalize(values) { - // It seems to be somewhat faster to do sorting first - return _arrayUnique(values.sort(sorter)); - } + normalize(values) { + // It seems to be somewhat faster to do sorting first + return _arrayUnique(values.sort(sorter)); + } } TimeScale.id = 'time'; @@ -615,26 +615,26 @@ TimeScale.id = 'time'; * @type {any} */ TimeScale.defaults = { - /** + /** * Scale boundary strategy (bypassed by min/max time options) * - `data`: make sure data are fully visible, ticks outside are removed * - `ticks`: make sure ticks are fully visible, data outside are truncated * @see https://github.com/chartjs/Chart.js/pull/4556 * @since 2.7.0 */ - bounds: 'data', - - adapters: {}, - time: { - parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - isoWeekday: false, // override week start day - minUnit: 'millisecond', - displayFormats: {} - }, - ticks: { - /** + bounds: 'data', + + adapters: {}, + time: { + parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + isoWeekday: false, // override week start day + minUnit: 'millisecond', + displayFormats: {} + }, + ticks: { + /** * Ticks generation input values: * - 'auto': generates "optimal" ticks based on scale size and time options. * - 'data': generates ticks from data (including labels from data {t|x|y} objects). @@ -642,10 +642,10 @@ TimeScale.defaults = { * @see https://github.com/chartjs/Chart.js/pull/4507 * @since 2.7.0 */ - source: 'auto', + source: 'auto', - major: { - enabled: false - } - } + major: { + enabled: false + } + } }; diff --git a/src/scales/scale.timeseries.js b/src/scales/scale.timeseries.js index 232b49d2209..f8228fe3431 100644 --- a/src/scales/scale.timeseries.js +++ b/src/scales/scale.timeseries.js @@ -11,52 +11,52 @@ import {isNullOrUndef} from '../helpers/helpers.core'; * @return {object} */ function interpolate(table, val, reverse) { - let prevSource, nextSource, prevTarget, nextTarget; - - // Note: the lookup table ALWAYS contains at least 2 items (min and max) - if (reverse) { - prevSource = Math.floor(val); - nextSource = Math.ceil(val); - prevTarget = table[prevSource]; - nextTarget = table[nextSource]; - } else { - const result = _lookup(table, val); - prevTarget = result.lo; - nextTarget = result.hi; - prevSource = table[prevTarget]; - nextSource = table[nextTarget]; - } - - const span = nextSource - prevSource; - return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget; + let prevSource, nextSource, prevTarget, nextTarget; + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + if (reverse) { + prevSource = Math.floor(val); + nextSource = Math.ceil(val); + prevTarget = table[prevSource]; + nextTarget = table[nextSource]; + } else { + const result = _lookup(table, val); + prevTarget = result.lo; + nextTarget = result.hi; + prevSource = table[prevTarget]; + nextSource = table[nextTarget]; + } + + const span = nextSource - prevSource; + return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget; } class TimeSeriesScale extends TimeScale { - /** + /** * @param {object} props */ - constructor(props) { - super(props); + constructor(props) { + super(props); - /** @type {object[]} */ - this._table = []; - /** @type {number} */ - this._maxIndex = undefined; - } + /** @type {object[]} */ + this._table = []; + /** @type {number} */ + this._maxIndex = undefined; + } - /** + /** * @protected */ - initOffsets() { - const me = this; - const timestamps = me._getTimestampsForTable(); - me._table = me.buildLookupTable(timestamps); - me._maxIndex = me._table.length - 1; - super.initOffsets(timestamps); - } - - /** + initOffsets() { + const me = this; + const timestamps = me._getTimestampsForTable(); + me._table = me.buildLookupTable(timestamps); + me._maxIndex = me._table.length - 1; + super.initOffsets(timestamps); + } + + /** * Returns an array of {time, pos} objects used to interpolate a specific `time` or position * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other @@ -67,89 +67,89 @@ class TimeSeriesScale extends TimeScale { * @return {object[]} * @protected */ - buildLookupTable(timestamps) { - const me = this; - const {min, max} = me; - if (!timestamps.length) { - return [ - {time: min, pos: 0}, - {time: max, pos: 1} - ]; - } - - const items = [min]; - let i, ilen, curr; - - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - curr = timestamps[i]; - if (curr > min && curr < max) { - items.push(curr); - } - } - - items.push(max); - - return items; - } - - /** + buildLookupTable(timestamps) { + const me = this; + const {min, max} = me; + if (!timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + const items = [min]; + let i, ilen, curr; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + return items; + } + + /** * Returns all timestamps * @return {number[]} * @private */ - _getTimestampsForTable() { - const me = this; - let timestamps = me._cache.all || []; - - if (timestamps.length) { - return timestamps; - } - - const data = me.getDataTimestamps(); - const label = me.getLabelTimestamps(); - if (data.length && label.length) { - // If combining labels and data (data might not contain all labels), - // we need to recheck uniqueness and sort - timestamps = me.normalize(data.concat(label)); - } else { - timestamps = data.length ? data : label; - } - timestamps = me._cache.all = timestamps; - - return timestamps; - } - - /** + _getTimestampsForTable() { + const me = this; + let timestamps = me._cache.all || []; + + if (timestamps.length) { + return timestamps; + } + + const data = me.getDataTimestamps(); + const label = me.getLabelTimestamps(); + if (data.length && label.length) { + // If combining labels and data (data might not contain all labels), + // we need to recheck uniqueness and sort + timestamps = me.normalize(data.concat(label)); + } else { + timestamps = data.length ? data : label; + } + timestamps = me._cache.all = timestamps; + + return timestamps; + } + + /** * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC) * @param {number} [index] * @return {number} */ - getPixelForValue(value, index) { - const me = this; - const offsets = me._offsets; - const pos = me._normalized && me._maxIndex > 0 && !isNullOrUndef(index) - ? index / me._maxIndex : me.getDecimalForValue(value); - return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); - } - - /** + getPixelForValue(value, index) { + const me = this; + const offsets = me._offsets; + const pos = me._normalized && me._maxIndex > 0 && !isNullOrUndef(index) + ? index / me._maxIndex : me.getDecimalForValue(value); + return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); + } + + /** * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC) * @return {number} */ - getDecimalForValue(value) { - return interpolate(this._table, value) / this._maxIndex; - } + getDecimalForValue(value) { + return interpolate(this._table, value) / this._maxIndex; + } - /** + /** * @param {number} pixel * @return {number} */ - getValueForPixel(pixel) { - const me = this; - const offsets = me._offsets; - const decimal = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; - return interpolate(me._table, decimal * this._maxIndex, true); - } + getValueForPixel(pixel) { + const me = this; + const offsets = me._offsets; + const decimal = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + return interpolate(me._table, decimal * this._maxIndex, true); + } } TimeSeriesScale.id = 'timeseries'; diff --git a/test/BasicChartWebWorker.js b/test/BasicChartWebWorker.js index 867969a719b..ca267713eb6 100644 --- a/test/BasicChartWebWorker.js +++ b/test/BasicChartWebWorker.js @@ -9,19 +9,19 @@ importScripts('../src/chart.js'); onmessage = function(event) { - try { - const {type, canvas} = event.data; - if (type !== 'initialize') { - throw new Error('invalid message type received by worker: ' + type); - } + try { + const {type, canvas} = event.data; + if (type !== 'initialize') { + throw new Error('invalid message type received by worker: ' + type); + } - const chart = new Chart(canvas); - if (!(chart.platform instanceof Chart.platforms.BasicPlatform)) { - throw new Error('did not use basic platform for chart in web worker'); - } + const chart = new Chart(canvas); + if (!(chart.platform instanceof Chart.platforms.BasicPlatform)) { + throw new Error('did not use basic platform for chart in web worker'); + } - postMessage({type: 'success'}); - } catch (error) { - postMessage({type: 'error', errorMessage: error.stack}); - } + postMessage({type: 'success'}); + } catch (error) { + postMessage({type: 'error', errorMessage: error.stack}); + } }; diff --git a/test/fixtures/controller.bar/backgroundColor/indexable.js b/test/fixtures/controller.bar/backgroundColor/indexable.js index a3a2bbeaf99..ca02427e5b5 100644 --- a/test/fixtures/controller.bar/backgroundColor/indexable.js +++ b/test/fixtures/controller.bar/backgroundColor/indexable.js @@ -1,50 +1,50 @@ 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: { - elements: { - bar: { - backgroundColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ] - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ] + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/backgroundColor/loopable.js b/test/fixtures/controller.bar/backgroundColor/loopable.js index 78616b6fe2b..345b85038b4 100644 --- a/test/fixtures/controller.bar/backgroundColor/loopable.js +++ b/test/fixtures/controller.bar/backgroundColor/loopable.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 3, 4, 5, 6], - backgroundColor: [ - '#ff0000', - '#00ff00', - '#0000ff' - ] - }, - { - // option in element (fallback) - data: [6, 5, 4, 3, 2, 1], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: [ - '#000000', - '#888888' - ] - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 3, 4, 5, 6], + backgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff' + ] + }, + { + // option in element (fallback) + data: [6, 5, 4, 3, 2, 1], + } + ] + }, + options: { + elements: { + bar: { + backgroundColor: [ + '#000000', + '#888888' + ] + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/backgroundColor/scriptable.js b/test/fixtures/controller.bar/backgroundColor/scriptable.js index 4f9d9e5ce82..97b0829df28 100644 --- a/test/fixtures/controller.bar/backgroundColor/scriptable.js +++ b/test/fixtures/controller.bar/backgroundColor/scriptable.js @@ -1,51 +1,51 @@ 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: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5] - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 0 ? '#0000ff' - : value > -8 ? '#ff0000' - : '#00ff00'; - } - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + elements: { + bar: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + } + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/backgroundColor/value.js b/test/fixtures/controller.bar/backgroundColor/value.js index 0a56893aabd..0979ae32b34 100644 --- a/test/fixtures/controller.bar/backgroundColor/value.js +++ b/test/fixtures/controller.bar/backgroundColor/value.js @@ -1,36 +1,36 @@ 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: { - elements: { - bar: { - backgroundColor: '#00ff00' - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: '#00ff00' + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/bar-base-value.js b/test/fixtures/controller.bar/bar-base-value.js index 56842233873..3f576f1a7c4 100644 --- a/test/fixtures/controller.bar/bar-base-value.js +++ b/test/fixtures/controller.bar/bar-base-value.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 3, 4], - datasets: [ - { - data: [5, 20, 10, 11], - base: 10, - backgroundColor: '#00ff00', - borderColor: '#ff0000', - borderWidth: 2, - } - ] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, 10, 11], + base: 10, + backgroundColor: '#00ff00', + borderColor: '#ff0000', + borderWidth: 2, + } + ] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/bar-default-begin-at-zero.js b/test/fixtures/controller.bar/bar-default-begin-at-zero.js index acd24711ace..1f2b4b8afea 100644 --- a/test/fixtures/controller.bar/bar-default-begin-at-zero.js +++ b/test/fixtures/controller.bar/bar-default-begin-at-zero.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 3, 4], - datasets: [ - { - data: [5, 20, 1, 10], - backgroundColor: '#00ff00', - borderColor: '#ff0000' - } - ] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, 1, 10], + backgroundColor: '#00ff00', + borderColor: '#ff0000' + } + ] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/bar-skip-null-object-data.js b/test/fixtures/controller.bar/bar-skip-null-object-data.js index b0b52688c7a..b21ec623c5d 100644 --- a/test/fixtures/controller.bar/bar-skip-null-object-data.js +++ b/test/fixtures/controller.bar/bar-skip-null-object-data.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [ - { - data: {0: 5, 1: 20, 2: 1, 3: 10}, - backgroundColor: '#00ff00', - borderColor: '#ff0000' - }, - { - data: {0: 10, 1: null, 2: 1, 3: NaN}, - backgroundColor: '#ff0000', - borderColor: '#ff0000' - } - ] - }, - options: { - skipNull: true, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [ + { + data: {0: 5, 1: 20, 2: 1, 3: 10}, + backgroundColor: '#00ff00', + borderColor: '#ff0000' + }, + { + data: {0: 10, 1: null, 2: 1, 3: NaN}, + backgroundColor: '#ff0000', + borderColor: '#ff0000' + } + ] + }, + options: { + skipNull: true, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/bar-skip-null.js b/test/fixtures/controller.bar/bar-skip-null.js index 4205a1627ac..fbbdaf0ce66 100644 --- a/test/fixtures/controller.bar/bar-skip-null.js +++ b/test/fixtures/controller.bar/bar-skip-null.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 3, 4], - datasets: [ - { - data: [5, 20, 1, 10], - backgroundColor: '#00ff00', - borderColor: '#ff0000' - }, - { - data: [10, null, 1, undefined], - backgroundColor: '#ff0000', - borderColor: '#ff0000' - } - ] - }, - options: { - skipNull: true, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, 1, 10], + backgroundColor: '#00ff00', + borderColor: '#ff0000' + }, + { + data: [10, null, 1, undefined], + backgroundColor: '#ff0000', + borderColor: '#ff0000' + } + ] + }, + options: { + skipNull: true, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/border-radius.js b/test/fixtures/controller.bar/border-radius.js index efe6afa9d05..cf9f96e998b 100644 --- a/test/fixtures/controller.bar/border-radius.js +++ b/test/fixtures/controller.bar/border-radius.js @@ -1,43 +1,43 @@ module.exports = { - threshold: 0.01, - config: { - type: 'bar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderWidth: 2, - borderRadius: 5 - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - borderSkipped: false, - borderRadius: Number.MAX_VALUE - } - ] - }, - options: { - indexAxis: 'y', - elements: { - bar: { - backgroundColor: '#AAAAAA80', - borderColor: '#80808080', - borderWidth: {bottom: 6, left: 15, top: 6, right: 15} - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + threshold: 0.01, + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: 2, + borderRadius: 5 + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderRadius: Number.MAX_VALUE + } + ] + }, + options: { + indexAxis: 'y', + elements: { + bar: { + backgroundColor: '#AAAAAA80', + borderColor: '#80808080', + borderWidth: {bottom: 6, left: 15, top: 6, right: 15} + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderColor/indexable.js b/test/fixtures/controller.bar/borderColor/indexable.js index f8ad9d01a97..9a0e315c382 100644 --- a/test/fixtures/controller.bar/borderColor/indexable.js +++ b/test/fixtures/controller.bar/borderColor/indexable.js @@ -1,52 +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], - borderColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - borderWidth: 8 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + borderWidth: 8 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderColor/scriptable.js b/test/fixtures/controller.bar/borderColor/scriptable.js index 9ba3dbcfd88..4b541ac6df2 100644 --- a/test/fixtures/controller.bar/borderColor/scriptable.js +++ b/test/fixtures/controller.bar/borderColor/scriptable.js @@ -1,53 +1,53 @@ 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: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5] - } - ] - }, - options: { - elements: { - bar: { - 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: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + elements: { + bar: { + 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: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderColor/value.js b/test/fixtures/controller.bar/borderColor/value.js index b8981e681f4..12cab8bab46 100644 --- a/test/fixtures/controller.bar/borderColor/value.js +++ b/test/fixtures/controller.bar/borderColor/value.js @@ -1,38 +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], - borderColor: '#ff0000' - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#00ff00', - borderWidth: 8 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#00ff00', + borderWidth: 8 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderSkipped/indexable.js b/test/fixtures/controller.bar/borderSkipped/indexable.js index e9bddedef84..66757821cc0 100644 --- a/test/fixtures/controller.bar/borderSkipped/indexable.js +++ b/test/fixtures/controller.bar/borderSkipped/indexable.js @@ -1,53 +1,53 @@ 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: [ - 'top', - 'top', - 'right', - 'right', - 'bottom', - 'left' - ] - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: 8, - borderSkipped: [ - 'bottom', - 'bottom', - 'left', - 'left', - 'top', - 'right' - ] - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: [ + 'top', + 'top', + 'right', + 'right', + 'bottom', + 'left' + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: 8, + borderSkipped: [ + 'bottom', + 'bottom', + 'left', + 'left', + 'top', + 'right' + ] + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderSkipped/scriptable.js b/test/fixtures/controller.bar/borderSkipped/scriptable.js index 3aa5a522248..a4a71041ec2 100644 --- a/test/fixtures/controller.bar/borderSkipped/scriptable.js +++ b/test/fixtures/controller.bar/borderSkipped/scriptable.js @@ -1,54 +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: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? 'left' - : value > 0 ? 'right' - : value > -8 ? 'top' - : 'bottom'; - } - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5] - } - ] - }, - options: { - elements: { - bar: { - 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: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? 'left' + : value > 0 ? 'right' + : value > -8 ? 'top' + : 'bottom'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + elements: { + bar: { + 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: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderSkipped/value.js b/test/fixtures/controller.bar/borderSkipped/value.js index 3e0602eb99f..4227ab7aa0c 100644 --- a/test/fixtures/controller.bar/borderSkipped/value.js +++ b/test/fixtures/controller.bar/borderSkipped/value.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [0, 5, -10, null], - borderSkipped: 'top' - }, - { - // option in dataset - data: [0, 5, -10, null], - borderSkipped: 'right' - }, - { - // option in dataset - data: [0, 5, -10, null], - borderSkipped: 'bottom' - }, - { - // option in element (fallback) - data: [0, 5, -10, null], - }, - { - // option in dataset - data: [0, 5, -10, null], - borderSkipped: false - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderSkipped: 'left', - borderWidth: 8 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: 'top' + }, + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: 'right' + }, + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: 'bottom' + }, + { + // option in element (fallback) + data: [0, 5, -10, null], + }, + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: false + } + ] + }, + options: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderSkipped: 'left', + borderWidth: 8 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.js b/test/fixtures/controller.bar/borderWidth/indexable-object.js index 6e7b6ad8447..1c1346f26f4 100644 --- a/test/fixtures/controller.bar/borderWidth/indexable-object.js +++ b/test/fixtures/controller.bar/borderWidth/indexable-object.js @@ -1,54 +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: [ - {}, - {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: { - elements: { - bar: { - 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: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + 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: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/indexable.js b/test/fixtures/controller.bar/borderWidth/indexable.js index b6f87da594d..feb4ec34696 100644 --- a/test/fixtures/controller.bar/borderWidth/indexable.js +++ b/test/fixtures/controller.bar/borderWidth/indexable.js @@ -1,52 +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: [ - 0, - 1, - 2, - 3, - 4, - 5 - ] - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: [ - 5, - 4, - 3, - 2, - 1, - 0 - ] - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: [ + 5, + 4, + 3, + 2, + 1, + 0 + ] + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/negative.js b/test/fixtures/controller.bar/borderWidth/negative.js index 39dc68fe5f0..84c2145df29 100644 --- a/test/fixtures/controller.bar/borderWidth/negative.js +++ b/test/fixtures/controller.bar/borderWidth/negative.js @@ -1,47 +1,47 @@ 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: { - elements: { - bar: { - backgroundColor: '#888', - borderColor: '#f00', - borderWidth: -4 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: '#888', + borderColor: '#f00', + borderWidth: -4 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/object.js b/test/fixtures/controller.bar/borderWidth/object.js index c69c6c53762..0c24e0dfb24 100644 --- a/test/fixtures/controller.bar/borderWidth/object.js +++ b/test/fixtures/controller.bar/borderWidth/object.js @@ -1,40 +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], - borderSkipped: false, - borderWidth: {bottom: 1, left: 2, top: 3, right: 4} - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderSkipped: false, - borderWidth: {bottom: 4, left: 3, top: 2, right: 1} - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderSkipped: false, + borderWidth: {bottom: 4, left: 3, top: 2, right: 1} + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.js b/test/fixtures/controller.bar/borderWidth/scriptable-object.js index a9a5154776c..3db3e2b57b2 100644 --- a/test/fixtures/controller.bar/borderWidth/scriptable-object.js +++ b/test/fixtures/controller.bar/borderWidth/scriptable-object.js @@ -1,48 +1,48 @@ 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: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#80808080', - borderSkipped: false, - borderWidth: function(ctx) { - return {left: ctx.dataIndex * 2}; - } - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#80808080', + borderSkipped: false, + borderWidth: function(ctx) { + return {left: ctx.dataIndex * 2}; + } + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/scriptable.js b/test/fixtures/controller.bar/borderWidth/scriptable.js index f2492b96b20..ed9e62dd1c8 100644 --- a/test/fixtures/controller.bar/borderWidth/scriptable.js +++ b/test/fixtures/controller.bar/borderWidth/scriptable.js @@ -1,46 +1,46 @@ 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: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: function(ctx) { - return ctx.dataIndex * 2; - } - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: function(ctx) { + return ctx.dataIndex * 2; + } + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/borderWidth/value.js b/test/fixtures/controller.bar/borderWidth/value.js index bfedc41ad72..d5e5dedf022 100644 --- a/test/fixtures/controller.bar/borderWidth/value.js +++ b/test/fixtures/controller.bar/borderWidth/value.js @@ -1,38 +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], - borderWidth: 2 - }, - { - // option in element (fallback) - data: [0, 5, 10, null, -10, -5], - } - ] - }, - options: { - elements: { - bar: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: 4 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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: { + elements: { + bar: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: 4 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/chart-area-clip.js b/test/fixtures/controller.bar/chart-area-clip.js index ecd2c1b7d54..78b85ed0650 100644 --- a/test/fixtures/controller.bar/chart-area-clip.js +++ b/test/fixtures/controller.bar/chart-area-clip.js @@ -1,40 +1,40 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 3, 4], - datasets: [ - { - data: [5, 20, -5, -20], - borderColor: '#ff0000' - } - ] - }, - options: { - layout: { - padding: { - left: 0, - right: 0, - top: 50, - bottom: 50 - } - }, - elements: { - bar: { - backgroundColor: '#00ff00', - borderWidth: 8 - } - }, - scales: { - x: {display: false}, - y: {display: false, min: -10, max: 10} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, -5, -20], + borderColor: '#ff0000' + } + ] + }, + options: { + layout: { + padding: { + left: 0, + right: 0, + top: 50, + bottom: 50 + } + }, + elements: { + bar: { + backgroundColor: '#00ff00', + borderWidth: 8 + } + }, + scales: { + x: {display: false}, + y: {display: false, min: -10, max: 10} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/data/object.js b/test/fixtures/controller.bar/data/object.js index bba10f006cf..ae1cd0a58c3 100644 --- a/test/fixtures/controller.bar/data/object.js +++ b/test/fixtures/controller.bar/data/object.js @@ -1,30 +1,30 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: ['a', 'b', 'c'], - datasets: [ - { - data: {a: 10, b: 2, c: -5}, - backgroundColor: '#ff0000' - }, - { - data: {a: 8, b: 12, c: 5}, - backgroundColor: '#00ff00' - } - ] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: ['a', 'b', 'c'], + datasets: [ + { + data: {a: 10, b: 2, c: -5}, + backgroundColor: '#ff0000' + }, + { + data: {a: 8, b: 12, c: 5}, + backgroundColor: '#00ff00' + } + ] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/data/parsing.js b/test/fixtures/controller.bar/data/parsing.js index adf3a67a668..d4bdbb31402 100644 --- a/test/fixtures/controller.bar/data/parsing.js +++ b/test/fixtures/controller.bar/data/parsing.js @@ -1,44 +1,44 @@ const data = [{x: 'Jan', net: 100, cogs: 50, gm: 50}, {x: 'Feb', net: 120, cogs: 55, gm: 75}]; module.exports = { - config: { - type: 'bar', - data: { - labels: ['Jan', 'Feb'], - datasets: [{ - label: 'Net sales', - backgroundColor: 'blue', - data: data, - parsing: { - yAxisKey: 'net' - } - }, { - label: 'Cost of goods sold', - backgroundColor: 'red', - data: data, - parsing: { - yAxisKey: 'cogs' - } - }, { - label: 'Gross margin', - backgroundColor: 'green', - data: data, - parsing: { - yAxisKey: 'gm' - } - }] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: ['Jan', 'Feb'], + datasets: [{ + label: 'Net sales', + backgroundColor: 'blue', + data: data, + parsing: { + yAxisKey: 'net' + } + }, { + label: 'Cost of goods sold', + backgroundColor: 'red', + data: data, + parsing: { + yAxisKey: 'cogs' + } + }, { + label: 'Gross margin', + backgroundColor: 'green', + data: data, + parsing: { + yAxisKey: 'gm' + } + }] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/floatBar/data-as-objects-horizontal.js b/test/fixtures/controller.bar/floatBar/data-as-objects-horizontal.js index 16a67c438a6..8d6f373183d 100644 --- a/test/fixtures/controller.bar/floatBar/data-as-objects-horizontal.js +++ b/test/fixtures/controller.bar/floatBar/data-as-objects-horizontal.js @@ -1,31 +1,31 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: ['a', 'b', 'c'], - datasets: [ - { - data: [{y: 'b', x: [2, 8]}, {y: 'c', x: [2, 5]}], - backgroundColor: '#ff0000' - }, - { - data: [{y: 'a', x: 10}, {y: 'c', x: [6, 10]}], - backgroundColor: '#00ff00' - } - ] - }, - options: { - indexAxis: 'y', - scales: { - x: {display: false, min: 0}, - y: {display: false, stacked: true} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: ['a', 'b', 'c'], + datasets: [ + { + data: [{y: 'b', x: [2, 8]}, {y: 'c', x: [2, 5]}], + backgroundColor: '#ff0000' + }, + { + data: [{y: 'a', x: 10}, {y: 'c', x: [6, 10]}], + backgroundColor: '#00ff00' + } + ] + }, + options: { + indexAxis: 'y', + scales: { + x: {display: false, min: 0}, + y: {display: false, stacked: true} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/floatBar/data-as-objects.js b/test/fixtures/controller.bar/floatBar/data-as-objects.js index 9d6fd63cada..42654bca24e 100644 --- a/test/fixtures/controller.bar/floatBar/data-as-objects.js +++ b/test/fixtures/controller.bar/floatBar/data-as-objects.js @@ -1,30 +1,30 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: ['a', 'b', 'c'], - datasets: [ - { - data: [{x: 'b', y: [2, 8]}, {x: 'c', y: [2, 5]}], - backgroundColor: '#ff0000' - }, - { - data: [{x: 'a', y: 10}, {x: 'c', y: [6, 10]}], - backgroundColor: '#00ff00' - } - ] - }, - options: { - scales: { - x: {display: false, stacked: true}, - y: {display: false, min: 0} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: ['a', 'b', 'c'], + datasets: [ + { + data: [{x: 'b', y: [2, 8]}, {x: 'c', y: [2, 5]}], + backgroundColor: '#ff0000' + }, + { + data: [{x: 'a', y: 10}, {x: 'c', y: [6, 10]}], + backgroundColor: '#00ff00' + } + ] + }, + options: { + scales: { + x: {display: false, stacked: true}, + y: {display: false, min: 0} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/horizontal-borders.js b/test/fixtures/controller.bar/horizontal-borders.js index 894d121e870..bcba2d89afb 100644 --- a/test/fixtures/controller.bar/horizontal-borders.js +++ b/test/fixtures/controller.bar/horizontal-borders.js @@ -1,41 +1,41 @@ module.exports = { - threshold: 0.01, - 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], - borderSkipped: false - } - ] - }, - options: { - indexAxis: 'y', - elements: { - bar: { - backgroundColor: '#AAAAAA80', - borderColor: '#80808080', - borderWidth: {bottom: 6, left: 15, top: 6, right: 15} - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + threshold: 0.01, + 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], + borderSkipped: false + } + ] + }, + options: { + indexAxis: 'y', + elements: { + bar: { + backgroundColor: '#AAAAAA80', + borderColor: '#80808080', + borderWidth: {bottom: 6, left: 15, top: 6, right: 15} + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/horizontal-neg.js b/test/fixtures/controller.bar/minBarLength/horizontal-neg.js index 0d8feddb153..85e6dba68e5 100644 --- a/test/fixtures/controller.bar/minBarLength/horizontal-neg.js +++ b/test/fixtures/controller.bar/minBarLength/horizontal-neg.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2], - datasets: [ - { - data: [0, -0.01, -30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - indexAxis: 'y', - scales: { - x: { - ticks: { - display: false - } - }, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2], + datasets: [ + { + data: [0, -0.01, -30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + indexAxis: 'y', + scales: { + x: { + ticks: { + display: false + } + }, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/horizontal-pos.js b/test/fixtures/controller.bar/minBarLength/horizontal-pos.js index a76feb1079a..d12c91cf9d7 100644 --- a/test/fixtures/controller.bar/minBarLength/horizontal-pos.js +++ b/test/fixtures/controller.bar/minBarLength/horizontal-pos.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2], - datasets: [ - { - data: [0, 0.01, 30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - indexAxis: 'y', - scales: { - x: { - ticks: { - display: false - } - }, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2], + datasets: [ + { + data: [0, 0.01, 30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + indexAxis: 'y', + scales: { + x: { + ticks: { + display: false + } + }, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/horizontal.js b/test/fixtures/controller.bar/minBarLength/horizontal.js index 2535bcba503..ac320120c4c 100644 --- a/test/fixtures/controller.bar/minBarLength/horizontal.js +++ b/test/fixtures/controller.bar/minBarLength/horizontal.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2, 3, 4], - datasets: [ - { - data: [0, -0.01, 0.01, 30, -30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - indexAxis: 'y', - scales: { - x: { - ticks: { - display: false - } - }, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4], + datasets: [ + { + data: [0, -0.01, 0.01, 30, -30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + indexAxis: 'y', + scales: { + x: { + ticks: { + display: false + } + }, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/vertical-neg.js b/test/fixtures/controller.bar/minBarLength/vertical-neg.js index 7ae3897b857..92f853f426f 100644 --- a/test/fixtures/controller.bar/minBarLength/vertical-neg.js +++ b/test/fixtures/controller.bar/minBarLength/vertical-neg.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2], - datasets: [ - { - data: [0, -0.01, -30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - scales: { - x: {display: false}, - y: { - ticks: { - display: false - } - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2], + datasets: [ + { + data: [0, -0.01, -30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + scales: { + x: {display: false}, + y: { + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/vertical-pos.js b/test/fixtures/controller.bar/minBarLength/vertical-pos.js index 07a03c4fd7e..cf0afa2b14b 100644 --- a/test/fixtures/controller.bar/minBarLength/vertical-pos.js +++ b/test/fixtures/controller.bar/minBarLength/vertical-pos.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2], - datasets: [ - { - data: [0, 0.01, 30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - scales: { - x: {display: false}, - y: { - ticks: { - display: false - } - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2], + datasets: [ + { + data: [0, 0.01, 30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + scales: { + x: {display: false}, + y: { + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/minBarLength/vertical.js b/test/fixtures/controller.bar/minBarLength/vertical.js index ff808c7b567..4a45513c53c 100644 --- a/test/fixtures/controller.bar/minBarLength/vertical.js +++ b/test/fixtures/controller.bar/minBarLength/vertical.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'bar', - data: { - labels: [0, 1, 2, 3, 4], - datasets: [ - { - data: [0, -0.01, 0.01, 30, -30], - backgroundColor: '#00ff00', - borderWidth: 0, - minBarLength: 20 - } - ] - }, - options: { - scales: { - x: {display: false}, - y: { - ticks: { - display: false - } - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4], + datasets: [ + { + data: [0, -0.01, 0.01, 30, -30], + backgroundColor: '#00ff00', + borderWidth: 0, + minBarLength: 20 + } + ] + }, + options: { + scales: { + x: {display: false}, + y: { + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.bar/stacking/logarithmic-strings.js b/test/fixtures/controller.bar/stacking/logarithmic-strings.js index 43763f67acd..96d9e4ba387 100644 --- a/test/fixtures/controller.bar/stacking/logarithmic-strings.js +++ b/test/fixtures/controller.bar/stacking/logarithmic-strings.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: ['10', '100', '10', '100'], - backgroundColor: '#ff0000' - }, { - data: ['100', '10', '0', '100'], - backgroundColor: '#00ff00' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - datasets: { - bar: { - barPercentage: 1, - } - }, - scales: { - x: { - type: 'category', - display: false, - stacked: true, - }, - y: { - type: 'logarithmic', - display: false, - stacked: true - } - } - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: ['10', '100', '10', '100'], + backgroundColor: '#ff0000' + }, { + data: ['100', '10', '0', '100'], + backgroundColor: '#00ff00' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + datasets: { + bar: { + barPercentage: 1, + } + }, + scales: { + x: { + type: 'category', + display: false, + stacked: true, + }, + y: { + type: 'logarithmic', + display: false, + stacked: true + } + } + } + } }; diff --git a/test/fixtures/controller.bar/stacking/logarithmic.js b/test/fixtures/controller.bar/stacking/logarithmic.js index a18911fed02..ca56ff5f88d 100644 --- a/test/fixtures/controller.bar/stacking/logarithmic.js +++ b/test/fixtures/controller.bar/stacking/logarithmic.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [10, 100, 10, 100], - backgroundColor: '#ff0000' - }, { - data: [100, 10, 0, 100], - backgroundColor: '#00ff00' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - datasets: { - bar: { - barPercentage: 1, - } - }, - scales: { - x: { - type: 'category', - display: false, - stacked: true, - }, - y: { - type: 'logarithmic', - display: false, - stacked: true - } - } - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [10, 100, 10, 100], + backgroundColor: '#ff0000' + }, { + data: [100, 10, 0, 100], + backgroundColor: '#00ff00' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + datasets: { + bar: { + barPercentage: 1, + } + }, + scales: { + x: { + type: 'category', + display: false, + stacked: true, + }, + y: { + type: 'logarithmic', + display: false, + stacked: true + } + } + } + } }; diff --git a/test/fixtures/controller.bubble/clip.js b/test/fixtures/controller.bubble/clip.js index 4a9d5090ea0..cf403c972cf 100644 --- a/test/fixtures/controller.bubble/clip.js +++ b/test/fixtures/controller.bubble/clip.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 5, 10, 15, 20, 25, 30, 50, 55, 60], - datasets: [{ - data: [6, 11, 10, 10, 3, 22, 7, 24], - type: 'bubble', - label: 'test', - borderColor: '#3e95cd', - fill: false - }] - }, - options: { - scales: { - x: {ticks: {display: false}}, - y: { - min: 8, - max: 25, - beginAtZero: true, - ticks: { - display: false - } - } - } - } - }, - options: { - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + labels: [0, 5, 10, 15, 20, 25, 30, 50, 55, 60], + datasets: [{ + data: [6, 11, 10, 10, 3, 22, 7, 24], + type: 'bubble', + label: 'test', + borderColor: '#3e95cd', + fill: false + }] + }, + options: { + scales: { + x: {ticks: {display: false}}, + y: { + min: 8, + max: 25, + beginAtZero: true, + ticks: { + display: false + } + } + } + } + }, + options: { + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/controller.bubble/radius-data.js b/test/fixtures/controller.bubble/radius-data.js index b1da990c6a0..bacd6a10f95 100644 --- a/test/fixtures/controller.bubble/radius-data.js +++ b/test/fixtures/controller.bubble/radius-data.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'bubble', - data: { - datasets: [{ - data: [ - {x: 0, y: 5, r: 1}, - {x: 1, y: 4, r: 2}, - {x: 2, y: 3, r: 6}, - {x: 3, y: 2}, - {x: 4, y: 1, r: 2}, - {x: 5, y: 0, r: NaN}, - {x: 6, y: -1, r: undefined}, - {x: 7, y: -2, r: null}, - {x: 8, y: -3, r: '4'}, - {x: 9, y: -4, r: '4px'}, - ] - }] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - }, - elements: { - point: { - backgroundColor: '#444', - radius: 10 - } - }, - layout: { - padding: { - left: 24, - right: 24 - } - } - } - }, - options: { - canvas: { - height: 128, - width: 256 - } - } + config: { + type: 'bubble', + data: { + datasets: [{ + data: [ + {x: 0, y: 5, r: 1}, + {x: 1, y: 4, r: 2}, + {x: 2, y: 3, r: 6}, + {x: 3, y: 2}, + {x: 4, y: 1, r: 2}, + {x: 5, y: 0, r: NaN}, + {x: 6, y: -1, r: undefined}, + {x: 7, y: -2, r: null}, + {x: 8, y: -3, r: '4'}, + {x: 9, y: -4, r: '4px'}, + ] + }] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + }, + elements: { + point: { + backgroundColor: '#444', + radius: 10 + } + }, + layout: { + padding: { + left: 24, + right: 24 + } + } + } + }, + options: { + canvas: { + height: 128, + width: 256 + } + } }; diff --git a/test/fixtures/controller.bubble/radius-scriptable.js b/test/fixtures/controller.bubble/radius-scriptable.js index 5706cb00d9d..4ceeac63fe6 100644 --- a/test/fixtures/controller.bubble/radius-scriptable.js +++ b/test/fixtures/controller.bubble/radius-scriptable.js @@ -1,43 +1,43 @@ 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: { - scales: { - x: {display: false}, - y: {display: false} - }, - elements: { - point: { - backgroundColor: '#444' - } - }, - layout: { - padding: { - left: 24, - right: 24 - } - } - } - }, - options: { - canvas: { - height: 128, - width: 256 - } - } + 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: { + scales: { + x: {display: false}, + y: {display: false} + }, + elements: { + point: { + backgroundColor: '#444' + } + }, + layout: { + padding: { + left: 24, + right: 24 + } + } + } + }, + options: { + canvas: { + height: 128, + width: 256 + } + } }; diff --git a/test/fixtures/controller.doughnut/backgroundColor/indexable.js b/test/fixtures/controller.doughnut/backgroundColor/indexable.js index 7399d795327..46a6de396c2 100644 --- a/test/fixtures/controller.doughnut/backgroundColor/indexable.js +++ b/test/fixtures/controller.doughnut/backgroundColor/indexable.js @@ -1,46 +1,46 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ] - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ] + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/backgroundColor/scriptable.js b/test/fixtures/controller.doughnut/backgroundColor/scriptable.js index 1083523346f..c431349d8e7 100644 --- a/test/fixtures/controller.doughnut/backgroundColor/scriptable.js +++ b/test/fixtures/controller.doughnut/backgroundColor/scriptable.js @@ -1,44 +1,44 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/backgroundColor/value.js b/test/fixtures/controller.doughnut/backgroundColor/value.js index 3bf4d3e476d..5e887f24417 100644 --- a/test/fixtures/controller.doughnut/backgroundColor/value.js +++ b/test/fixtures/controller.doughnut/backgroundColor/value.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: '#ff0000' - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: '#00ff00' - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: '#00ff00' + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderAlign/indexable.js b/test/fixtures/controller.doughnut/borderAlign/indexable.js index 4f9d5572927..3b8a79ba505 100644 --- a/test/fixtures/controller.doughnut/borderAlign/indexable.js +++ b/test/fixtures/controller.doughnut/borderAlign/indexable.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: [ - 'center', - 'inner', - 'center', - 'inner', - 'center', - 'inner', - ], - borderColor: '#00ff00' - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff0000', - borderWidth: 5, - borderAlign: [ - 'center', - 'inner', - 'center', - 'inner', - 'center', - 'inner', - ] - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: [ + 'center', + 'inner', + 'center', + 'inner', + 'center', + 'inner', + ], + borderColor: '#00ff00' + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff0000', + borderWidth: 5, + borderAlign: [ + 'center', + 'inner', + 'center', + 'inner', + 'center', + 'inner', + ] + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderAlign/scriptable.js b/test/fixtures/controller.doughnut/borderAlign/scriptable.js index 81d14fba39e..27ba4bc43bf 100644 --- a/test/fixtures/controller.doughnut/borderAlign/scriptable.js +++ b/test/fixtures/controller.doughnut/borderAlign/scriptable.js @@ -1,42 +1,42 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 'inner' : 'center'; - }, - borderColor: '#0000ff', - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff00ff', - borderWidth: 8, - borderAlign: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 'center' : 'inner'; - } - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 'inner' : 'center'; + }, + borderColor: '#0000ff', + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff00ff', + borderWidth: 8, + borderAlign: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 'center' : 'inner'; + } + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderAlign/value.js b/test/fixtures/controller.doughnut/borderAlign/value.js index 6271ddbe9f8..31f71190ffb 100644 --- a/test/fixtures/controller.doughnut/borderAlign/value.js +++ b/test/fixtures/controller.doughnut/borderAlign/value.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: 'inner', - borderColor: '#00ff00', - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderAlign: 'center', - borderColor: '#0000ff', - borderWidth: 4, - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: 'inner', + borderColor: '#00ff00', + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderAlign: 'center', + borderColor: '#0000ff', + borderWidth: 4, + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderColor/indexable.js b/test/fixtures/controller.doughnut/borderColor/indexable.js index 2d6a8cddf3c..8035df7363d 100644 --- a/test/fixtures/controller.doughnut/borderColor/indexable.js +++ b/test/fixtures/controller.doughnut/borderColor/indexable.js @@ -1,48 +1,48 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - borderWidth: 8 - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + borderWidth: 8 + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderColor/scriptable.js b/test/fixtures/controller.doughnut/borderColor/scriptable.js index 0480889319b..95cda7ad7cd 100644 --- a/test/fixtures/controller.doughnut/borderColor/scriptable.js +++ b/test/fixtures/controller.doughnut/borderColor/scriptable.js @@ -1,46 +1,46 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 6 ? '#0000ff' - : value > 2 ? '#ff0000' - : '#00ff00'; - }, - borderWidth: 8 - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 6 ? '#0000ff' + : value > 2 ? '#ff0000' + : '#00ff00'; + }, + borderWidth: 8 + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderColor/value.js b/test/fixtures/controller.doughnut/borderColor/value.js index fbe14eacbbd..04c152f80d2 100644 --- a/test/fixtures/controller.doughnut/borderColor/value.js +++ b/test/fixtures/controller.doughnut/borderColor/value.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: '#ff0000' - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#00ff00', - borderWidth: 8 - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#00ff00', + borderWidth: 8 + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderWidth/indexable.js b/test/fixtures/controller.doughnut/borderWidth/indexable.js index 04b546fc88b..10eb61d7a50 100644 --- a/test/fixtures/controller.doughnut/borderWidth/indexable.js +++ b/test/fixtures/controller.doughnut/borderWidth/indexable.js @@ -1,48 +1,48 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: [ - 0, - 1, - 2, - 3, - 4, - 5 - ] - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: [ - 5, - 4, - 3, - 2, - 1, - 0 - ] - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: [ + 5, + 4, + 3, + 2, + 1, + 0 + ] + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderWidth/scriptable.js b/test/fixtures/controller.doughnut/borderWidth/scriptable.js index 41e6fca1926..94147ae5582 100644 --- a/test/fixtures/controller.doughnut/borderWidth/scriptable.js +++ b/test/fixtures/controller.doughnut/borderWidth/scriptable.js @@ -1,39 +1,39 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return Math.abs(value); - } - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: function(ctx) { - return ctx.dataIndex * 2; - } - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return Math.abs(value); + } + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: function(ctx) { + return ctx.dataIndex * 2; + } + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/borderWidth/value.js b/test/fixtures/controller.doughnut/borderWidth/value.js index ac4844370b5..52da58db22d 100644 --- a/test/fixtures/controller.doughnut/borderWidth/value.js +++ b/test/fixtures/controller.doughnut/borderWidth/value.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: 2 - }, - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: 4 - } - }, - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'doughnut', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: 2 + }, + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: 4 + } + }, + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js index 0b137ebbe5a..98361819e9d 100644 --- a/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js +++ b/test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js @@ -1,31 +1,31 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: ['A', 'B', 'C', 'D', 'E'], - datasets: [{ - data: [1, 5, 10, 50, 100], - backgroundColor: [ - 'rgba(255, 99, 132, 0.8)', - 'rgba(54, 162, 235, 0.8)', - 'rgba(255, 206, 86, 0.8)', - 'rgba(75, 192, 192, 0.8)', - 'rgba(153, 102, 255, 0.8)' - ], - borderWidth: 1, - borderColor: [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 206, 86)', - 'rgb(75, 192, 192)', - 'rgb(153, 102, 255)' - ], - circumference: 180 - }] - }, - options: { - circumference: 57.32, - responsive: false - } - } + config: { + type: 'doughnut', + data: { + labels: ['A', 'B', 'C', 'D', 'E'], + datasets: [{ + data: [1, 5, 10, 50, 100], + backgroundColor: [ + 'rgba(255, 99, 132, 0.8)', + 'rgba(54, 162, 235, 0.8)', + 'rgba(255, 206, 86, 0.8)', + 'rgba(75, 192, 192, 0.8)', + 'rgba(153, 102, 255, 0.8)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + circumference: 180 + }] + }, + options: { + circumference: 57.32, + responsive: false + } + } }; diff --git a/test/fixtures/controller.doughnut/doughnut-hidden.js b/test/fixtures/controller.doughnut/doughnut-hidden.js index 9ba98790230..07d0f8aca14 100644 --- a/test/fixtures/controller.doughnut/doughnut-hidden.js +++ b/test/fixtures/controller.doughnut/doughnut-hidden.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: ['A', 'B', 'C', 'D', 'E'], - datasets: [{ - data: [1, 5, 10, 50, 100], - backgroundColor: [ - 'rgba(255, 99, 132, 0.8)', - 'rgba(54, 162, 235, 0.8)', - 'rgba(255, 206, 86, 0.8)', - 'rgba(75, 192, 192, 0.8)', - 'rgba(153, 102, 255, 0.8)' - ], - borderWidth: 4, - borderColor: [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 206, 86)', - 'rgb(75, 192, 192)', - 'rgb(153, 102, 255)' - ] - }] - }, - options: { - responsive: false, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: false - } - }, - plugins: [{ - id: 'hide', - afterInit(chart) { - chart.toggleDataVisibility(4); - } - }] - } + config: { + type: 'doughnut', + data: { + labels: ['A', 'B', 'C', 'D', 'E'], + datasets: [{ + data: [1, 5, 10, 50, 100], + backgroundColor: [ + 'rgba(255, 99, 132, 0.8)', + 'rgba(54, 162, 235, 0.8)', + 'rgba(255, 206, 86, 0.8)', + 'rgba(75, 192, 192, 0.8)', + 'rgba(153, 102, 255, 0.8)' + ], + borderWidth: 4, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ] + }] + }, + options: { + responsive: false, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: false + } + }, + plugins: [{ + id: 'hide', + afterInit(chart) { + chart.toggleDataVisibility(4); + } + }] + } }; diff --git a/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js index 57f60e95221..7d604ca8c26 100644 --- a/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js +++ b/test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'doughnut', - data: { - labels: ['A', 'B', 'C', 'D', 'E'], - datasets: [{ - data: [1, 5, 10, 50, 100], - backgroundColor: [ - 'rgba(255, 99, 132, 0.8)', - 'rgba(54, 162, 235, 0.8)', - 'rgba(255, 206, 86, 0.8)', - 'rgba(75, 192, 192, 0.8)', - 'rgba(153, 102, 255, 0.8)' - ], - borderWidth: 1, - borderColor: [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 206, 86)', - 'rgb(75, 192, 192)', - 'rgb(153, 102, 255)' - ], - rotation: -90 - }, { - data: [1, 5, 10, 50, 100], - backgroundColor: [ - 'rgba(255, 99, 132, 0.8)', - 'rgba(54, 162, 235, 0.8)', - 'rgba(255, 206, 86, 0.8)', - 'rgba(75, 192, 192, 0.8)', - 'rgba(153, 102, 255, 0.8)' - ], - borderWidth: 1, - borderColor: [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 206, 86)', - 'rgb(75, 192, 192)', - 'rgb(153, 102, 255)' - ], - rotation: 0 - }] - }, - options: { - circumference: 180, - responsive: false - } - } + config: { + type: 'doughnut', + data: { + labels: ['A', 'B', 'C', 'D', 'E'], + datasets: [{ + data: [1, 5, 10, 50, 100], + backgroundColor: [ + 'rgba(255, 99, 132, 0.8)', + 'rgba(54, 162, 235, 0.8)', + 'rgba(255, 206, 86, 0.8)', + 'rgba(75, 192, 192, 0.8)', + 'rgba(153, 102, 255, 0.8)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + rotation: -90 + }, { + data: [1, 5, 10, 50, 100], + backgroundColor: [ + 'rgba(255, 99, 132, 0.8)', + 'rgba(54, 162, 235, 0.8)', + 'rgba(255, 206, 86, 0.8)', + 'rgba(75, 192, 192, 0.8)', + 'rgba(153, 102, 255, 0.8)' + ], + borderWidth: 1, + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 206, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + rotation: 0 + }] + }, + options: { + circumference: 180, + responsive: false + } + } }; diff --git a/test/fixtures/controller.line/backgroundColor/scriptable.js b/test/fixtures/controller.line/backgroundColor/scriptable.js index b02960ab7ac..88dc039ab9a 100644 --- a/test/fixtures/controller.line/backgroundColor/scriptable.js +++ b/test/fixtures/controller.line/backgroundColor/scriptable.js @@ -1,63 +1,63 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [4, 5, 10, null, -10, -5], - backgroundColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [-4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: true, - backgroundColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#ff00ff'; - } - }, - point: { - backgroundColor: '#0000ff', - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [4, 5, 10, null, -10, -5], + backgroundColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [-4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: true, + backgroundColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#ff00ff'; + } + }, + point: { + backgroundColor: '#0000ff', + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/backgroundColor/value.js b/test/fixtures/controller.line/backgroundColor/value.js index adc20f9ef54..6a52e7b7e45 100644 --- a/test/fixtures/controller.line/backgroundColor/value.js +++ b/test/fixtures/controller.line/backgroundColor/value.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'line', - 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: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: true, - backgroundColor: '#00ff00' - }, - point: { - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + 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: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: true, + backgroundColor: '#00ff00' + }, + point: { + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderCapStyle/scriptable.js b/test/fixtures/controller.line/borderCapStyle/scriptable.js index cb31c79d025..6d76e2056a5 100644 --- a/test/fixtures/controller.line/borderCapStyle/scriptable.js +++ b/test/fixtures/controller.line/borderCapStyle/scriptable.js @@ -1,62 +1,62 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [null, 3, 3], - borderCapStyle: function(ctx) { - var index = (ctx.datasetIndex % 2); - return index === 0 ? 'round' - : index === 1 ? 'square' - : 'butt'; - } - }, - { - // option in element (fallback) - data: [null, 2, 2], - }, - { - // option in element (fallback) - data: [null, 1, 1], - } - ] - }, - options: { - elements: { - line: { - borderCapStyle: function(ctx) { - var index = (ctx.datasetIndex % 3); - return index === 0 ? 'round' - : index === 1 ? 'square' - : 'butt'; - }, - borderColor: '#ff0000', - borderWidth: 32, - fill: false - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [null, 3, 3], + borderCapStyle: function(ctx) { + var index = (ctx.datasetIndex % 2); + return index === 0 ? 'round' + : index === 1 ? 'square' + : 'butt'; + } + }, + { + // option in element (fallback) + data: [null, 2, 2], + }, + { + // option in element (fallback) + data: [null, 1, 1], + } + ] + }, + options: { + elements: { + line: { + borderCapStyle: function(ctx) { + var index = (ctx.datasetIndex % 3); + return index === 0 ? 'round' + : index === 1 ? 'square' + : 'butt'; + }, + borderColor: '#ff0000', + borderWidth: 32, + fill: false + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderCapStyle/value.js b/test/fixtures/controller.line/borderCapStyle/value.js index 2f0e514bdb5..25e8135b5eb 100644 --- a/test/fixtures/controller.line/borderCapStyle/value.js +++ b/test/fixtures/controller.line/borderCapStyle/value.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [null, 3, 3], - borderCapStyle: 'round', - }, - { - // option in dataset - data: [null, 2, 2], - borderCapStyle: 'square', - }, - { - // option in element (fallback) - data: [null, 1, 1], - } - ] - }, - options: { - elements: { - line: { - borderCapStyle: 'butt', - borderColor: '#00ff00', - borderWidth: 32, - fill: false, - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [null, 3, 3], + borderCapStyle: 'round', + }, + { + // option in dataset + data: [null, 2, 2], + borderCapStyle: 'square', + }, + { + // option in element (fallback) + data: [null, 1, 1], + } + ] + }, + options: { + elements: { + line: { + borderCapStyle: 'butt', + borderColor: '#00ff00', + borderWidth: 32, + fill: false, + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderColor/scriptable.js b/test/fixtures/controller.line/borderColor/scriptable.js index d87fd183a96..e3052f232f8 100644 --- a/test/fixtures/controller.line/borderColor/scriptable.js +++ b/test/fixtures/controller.line/borderColor/scriptable.js @@ -1,59 +1,59 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [4, 5, 10, null, -10, -5], - borderColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#0000ff'; - } - }, - { - // option in element (fallback) - data: [-4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#0000ff'; - }, - borderWidth: 10, - fill: false - }, - point: { - borderColor: '#ff0000', - borderWidth: 10, - radius: 16 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [4, 5, 10, null, -10, -5], + borderColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#0000ff'; + } + }, + { + // option in element (fallback) + data: [-4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#0000ff'; + }, + borderWidth: 10, + fill: false + }, + point: { + borderColor: '#ff0000', + borderWidth: 10, + radius: 16 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderColor/value.js b/test/fixtures/controller.line/borderColor/value.js index 9f58e2427be..7a269db5d5a 100644 --- a/test/fixtures/controller.line/borderColor/value.js +++ b/test/fixtures/controller.line/borderColor/value.js @@ -1,44 +1,44 @@ module.exports = { - config: { - type: 'line', - 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: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#0000ff', - fill: false, - }, - point: { - borderColor: '#0000ff', - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + 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: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#0000ff', + fill: false, + }, + point: { + borderColor: '#0000ff', + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderDash/scriptable.js b/test/fixtures/controller.line/borderDash/scriptable.js index 127414872f7..b6bf1010667 100644 --- a/test/fixtures/controller.line/borderDash/scriptable.js +++ b/test/fixtures/controller.line/borderDash/scriptable.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [4, 5, 10, null, -10, -5], - borderDash: function(ctx) { - return ctx.datasetIndex === 0 ? [5] : [10]; - } - }, - { - // option in element (fallback) - data: [-4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: function(ctx) { - return ctx.datasetIndex === 0 ? [5] : [10]; - } - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [4, 5, 10, null, -10, -5], + borderDash: function(ctx) { + return ctx.datasetIndex === 0 ? [5] : [10]; + } + }, + { + // option in element (fallback) + data: [-4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: function(ctx) { + return ctx.datasetIndex === 0 ? [5] : [10]; + } + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderDash/value.js b/test/fixtures/controller.line/borderDash/value.js index 9d1f8a6eb24..3f4f01a6b12 100644 --- a/test/fixtures/controller.line/borderDash/value.js +++ b/test/fixtures/controller.line/borderDash/value.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: '#ff0000', - borderDash: [5] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [10], - fill: false, - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#ff0000', + borderDash: [5] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [10], + fill: false, + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderDashOffset/scriptable.js b/test/fixtures/controller.line/borderDashOffset/scriptable.js index 4e56559dbae..5dabacc6e96 100644 --- a/test/fixtures/controller.line/borderDashOffset/scriptable.js +++ b/test/fixtures/controller.line/borderDashOffset/scriptable.js @@ -1,51 +1,51 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [1, 1, 1, 1], - borderColor: '#ff0000', - borderDash: [20], - borderDashOffset: function(ctx) { - return ctx.datasetIndex === 0 ? 5.0 : 0.0; - } - }, - { - // option in element (fallback) - data: [0, 0, 0, 0] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [20], - borderDashOffset: function(ctx) { - return ctx.datasetIndex === 0 ? 5.0 : 0.0; - }, - fill: false, - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [1, 1, 1, 1], + borderColor: '#ff0000', + borderDash: [20], + borderDashOffset: function(ctx) { + return ctx.datasetIndex === 0 ? 5.0 : 0.0; + } + }, + { + // option in element (fallback) + data: [0, 0, 0, 0] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [20], + borderDashOffset: function(ctx) { + return ctx.datasetIndex === 0 ? 5.0 : 0.0; + }, + fill: false, + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderDashOffset/value.js b/test/fixtures/controller.line/borderDashOffset/value.js index 89718503a2f..b16b12bcbd1 100644 --- a/test/fixtures/controller.line/borderDashOffset/value.js +++ b/test/fixtures/controller.line/borderDashOffset/value.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [1, 1, 1, 1, 1, 1], - borderColor: '#ff0000', - borderDash: [20], - borderDashOffset: 5.0 - }, - { - // option in element (fallback) - data: [0, 0, 0, 0, 0, 0] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [20], - borderDashOffset: 0.0, // default - fill: false, - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [1, 1, 1, 1, 1, 1], + borderColor: '#ff0000', + borderDash: [20], + borderDashOffset: 5.0 + }, + { + // option in element (fallback) + data: [0, 0, 0, 0, 0, 0] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [20], + borderDashOffset: 0.0, // default + fill: false, + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderJoinStyle/scriptable.js b/test/fixtures/controller.line/borderJoinStyle/scriptable.js index fd80e765db2..2fc77bc8e99 100644 --- a/test/fixtures/controller.line/borderJoinStyle/scriptable.js +++ b/test/fixtures/controller.line/borderJoinStyle/scriptable.js @@ -1,59 +1,59 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2], - datasets: [ - { - // option in dataset - data: [6, 18, 6], - borderColor: '#ff0000', - borderJoinStyle: function(ctx) { - var index = ctx.datasetIndex % 3; - return index === 0 ? 'round' - : index === 1 ? 'miter' - : 'bevel'; - } - }, - { - // option in element (fallback) - data: [2, 14, 2], - borderColor: '#0000ff', - }, - { - // option in element (fallback) - data: [-2, 10, -2] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderJoinStyle: function(ctx) { - var index = (ctx.datasetIndex % 3); - return index === 0 ? 'round' - : index === 1 ? 'miter' - : 'bevel'; - }, - borderWidth: 25, - fill: false, - tension: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2], + datasets: [ + { + // option in dataset + data: [6, 18, 6], + borderColor: '#ff0000', + borderJoinStyle: function(ctx) { + var index = ctx.datasetIndex % 3; + return index === 0 ? 'round' + : index === 1 ? 'miter' + : 'bevel'; + } + }, + { + // option in element (fallback) + data: [2, 14, 2], + borderColor: '#0000ff', + }, + { + // option in element (fallback) + data: [-2, 10, -2] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderJoinStyle: function(ctx) { + var index = (ctx.datasetIndex % 3); + return index === 0 ? 'round' + : index === 1 ? 'miter' + : 'bevel'; + }, + borderWidth: 25, + fill: false, + tension: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderJoinStyle/value.js b/test/fixtures/controller.line/borderJoinStyle/value.js index 3175f0f3b57..303c1a62a60 100644 --- a/test/fixtures/controller.line/borderJoinStyle/value.js +++ b/test/fixtures/controller.line/borderJoinStyle/value.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2], - datasets: [ - { - // option in dataset - data: [6, 18, 6], - borderColor: '#ff0000', - borderJoinStyle: 'round', - }, - { - // option in element (fallback) - data: [2, 14, 2], - borderColor: '#0000ff', - borderJoinStyle: 'bevel', - }, - { - // option in element (fallback) - data: [-2, 10, -2] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderJoinStyle: 'miter', - borderWidth: 25, - fill: false, - tension: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2], + datasets: [ + { + // option in dataset + data: [6, 18, 6], + borderColor: '#ff0000', + borderJoinStyle: 'round', + }, + { + // option in element (fallback) + data: [2, 14, 2], + borderColor: '#0000ff', + borderJoinStyle: 'bevel', + }, + { + // option in element (fallback) + data: [-2, 10, -2] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderJoinStyle: 'miter', + borderWidth: 25, + fill: false, + tension: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderWidth/scriptable.js b/test/fixtures/controller.line/borderWidth/scriptable.js index cef7246d682..81925c9dcfc 100644 --- a/test/fixtures/controller.line/borderWidth/scriptable.js +++ b/test/fixtures/controller.line/borderWidth/scriptable.js @@ -1,57 +1,57 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [4, 5, 10, null, -10, -5], - borderColor: '#0000ff', - borderWidth: function(ctx) { - var index = ctx.index; - return index % 2 ? 10 : 20; - }, - pointBorderColor: '#00ff00' - }, - { - // option in element (fallback) - data: [-4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#ff0000', - borderWidth: function(ctx) { - var index = ctx.index; - return index % 2 ? 10 : 20; - }, - fill: false, - }, - point: { - borderColor: '#00ff00', - borderWidth: 5, - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [4, 5, 10, null, -10, -5], + borderColor: '#0000ff', + borderWidth: function(ctx) { + var index = ctx.index; + return index % 2 ? 10 : 20; + }, + pointBorderColor: '#00ff00' + }, + { + // option in element (fallback) + data: [-4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#ff0000', + borderWidth: function(ctx) { + var index = ctx.index; + return index % 2 ? 10 : 20; + }, + fill: false, + }, + point: { + borderColor: '#00ff00', + borderWidth: 5, + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderWidth/value.js b/test/fixtures/controller.line/borderWidth/value.js index 67bd66bb30f..9c4889e7c3d 100644 --- a/test/fixtures/controller.line/borderWidth/value.js +++ b/test/fixtures/controller.line/borderWidth/value.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: '#0000ff', - borderWidth: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 3, - fill: false, - }, - point: { - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#0000ff', + borderWidth: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 3, + fill: false, + }, + point: { + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/borderWidth/zero.js b/test/fixtures/controller.line/borderWidth/zero.js index b5b821dea8b..4d13be9da98 100644 --- a/test/fixtures/controller.line/borderWidth/zero.js +++ b/test/fixtures/controller.line/borderWidth/zero.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: '#0000ff', - borderColor: '#0000ff', - borderWidth: 0 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 3, - fill: false, - }, - point: { - backgroundColor: '#00ff00', - radius: 10, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#0000ff', + borderColor: '#0000ff', + borderWidth: 0 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 3, + fill: false, + }, + point: { + backgroundColor: '#00ff00', + radius: 10, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/cubicInterpolationMode/scriptable.js b/test/fixtures/controller.line/cubicInterpolationMode/scriptable.js index f01007630e1..8333490dddd 100644 --- a/test/fixtures/controller.line/cubicInterpolationMode/scriptable.js +++ b/test/fixtures/controller.line/cubicInterpolationMode/scriptable.js @@ -1,48 +1,48 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 4, 2, 6, 4, 8], - borderColor: '#ff0000', - cubicInterpolationMode: function(ctx) { - return ctx.datasetIndex === 0 ? 'monotone' : 'default'; - } - }, - { - // option in element (fallback) - data: [2, 6, 4, 8, 6, 10], - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 20, - cubicInterpolationMode: function(ctx) { - return ctx.datasetIndex === 0 ? 'monotone' : 'default'; - }, - fill: false, - tension: 0.4 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 4, 2, 6, 4, 8], + borderColor: '#ff0000', + cubicInterpolationMode: function(ctx) { + return ctx.datasetIndex === 0 ? 'monotone' : 'default'; + } + }, + { + // option in element (fallback) + data: [2, 6, 4, 8, 6, 10], + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 20, + cubicInterpolationMode: function(ctx) { + return ctx.datasetIndex === 0 ? 'monotone' : 'default'; + }, + fill: false, + tension: 0.4 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/cubicInterpolationMode/value.js b/test/fixtures/controller.line/cubicInterpolationMode/value.js index f0d9143893f..2ae1a8abc68 100644 --- a/test/fixtures/controller.line/cubicInterpolationMode/value.js +++ b/test/fixtures/controller.line/cubicInterpolationMode/value.js @@ -1,44 +1,44 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 4, 2, 6, 4, 8], - borderColor: '#ff0000', - cubicInterpolationMode: 'monotone' - }, - { - // option in element (fallback) - data: [2, 6, 4, 8, 6, 10] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 20, - cubicInterpolationMode: 'default', - fill: false, - tension: 0.4 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 4, 2, 6, 4, 8], + borderColor: '#ff0000', + cubicInterpolationMode: 'monotone' + }, + { + // option in element (fallback) + data: [2, 6, 4, 8, 6, 10] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 20, + cubicInterpolationMode: 'default', + fill: false, + tension: 0.4 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/fill/order-default.js b/test/fixtures/controller.line/fill/order-default.js index 4fdfda18c75..072acbbc0e4 100644 --- a/test/fixtures/controller.line/fill/order-default.js +++ b/test/fixtures/controller.line/fill/order-default.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [3, 1, 2, 0, 8, 1], - backgroundColor: '#ff0000' - }, - { - // option in element (fallback) - data: [0, 4, 2, 6, 4, 8], - backgroundColor: '#00ff00' - } - ] - }, - options: { - elements: { - line: { - fill: true - }, - point: { - radius: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00' + } + ] + }, + options: { + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/fill/order.js b/test/fixtures/controller.line/fill/order.js index 6a05ac9c034..892043195ae 100644 --- a/test/fixtures/controller.line/fill/order.js +++ b/test/fixtures/controller.line/fill/order.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - data: [3, 1, 2, 0, 8, 1], - backgroundColor: '#ff0000', - order: 2 - }, - { - data: [0, 4, 2, 6, 4, 8], - backgroundColor: '#00ff00', - order: 1 - } - ] - }, - options: { - elements: { - line: { - fill: true - }, - point: { - radius: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000', + order: 2 + }, + { + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00', + order: 1 + } + ] + }, + options: { + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/fill/scriptable.js b/test/fixtures/controller.line/fill/scriptable.js index c6e82c95b56..61d946c857a 100644 --- a/test/fixtures/controller.line/fill/scriptable.js +++ b/test/fixtures/controller.line/fill/scriptable.js @@ -1,51 +1,51 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [-2, -6, -4, -8, -6, -10], - backgroundColor: '#ff0000', - fill: function(ctx) { - return ctx.datasetIndex === 0 ? true : false; - } - }, - { - // option in element (fallback) - data: [0, 4, 2, 6, 4, 8], - } - ] - }, - options: { - elements: { - line: { - backgroundColor: '#00ff00', - fill: function(ctx) { - return ctx.datasetIndex === 0 ? true : false; - } - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [-2, -6, -4, -8, -6, -10], + backgroundColor: '#ff0000', + fill: function(ctx) { + return ctx.datasetIndex === 0 ? true : false; + } + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + } + ] + }, + options: { + elements: { + line: { + backgroundColor: '#00ff00', + fill: function(ctx) { + return ctx.datasetIndex === 0 ? true : false; + } + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/fill/value.js b/test/fixtures/controller.line/fill/value.js index a10cc5205f6..aebe9f244d6 100644 --- a/test/fixtures/controller.line/fill/value.js +++ b/test/fixtures/controller.line/fill/value.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [-2, -6, -4, -8, -6, -10], - backgroundColor: '#ff0000', - fill: false - }, - { - // option in element (fallback) - data: [0, 4, 2, 6, 4, 8], - } - ] - }, - options: { - elements: { - line: { - backgroundColor: '#00ff00', - fill: true, - } - }, - layout: { - padding: 32 - }, - scales: { - x: {display: false}, - y: {display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [-2, -6, -4, -8, -6, -10], + backgroundColor: '#ff0000', + fill: false + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + } + ] + }, + options: { + elements: { + line: { + backgroundColor: '#00ff00', + fill: true, + } + }, + layout: { + padding: 32 + }, + scales: { + x: {display: false}, + y: {display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBackgroundColor/indexable.js b/test/fixtures/controller.line/pointBackgroundColor/indexable.js index a048f3c1917..26ef1ee5d2c 100644 --- a/test/fixtures/controller.line/pointBackgroundColor/indexable.js +++ b/test/fixtures/controller.line/pointBackgroundColor/indexable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - radius: 10 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + radius: 10 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBackgroundColor/scriptable.js b/test/fixtures/controller.line/pointBackgroundColor/scriptable.js index ba1200dbade..997a53bc711 100644 --- a/test/fixtures/controller.line/pointBackgroundColor/scriptable.js +++ b/test/fixtures/controller.line/pointBackgroundColor/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 0 ? '#0000ff' - : value > -8 ? '#ff0000' - : '#00ff00'; - }, - radius: 10, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + }, + radius: 10, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBackgroundColor/value.js b/test/fixtures/controller.line/pointBackgroundColor/value.js index ef5df3378f9..0a8cbb579a6 100644 --- a/test/fixtures/controller.line/pointBackgroundColor/value.js +++ b/test/fixtures/controller.line/pointBackgroundColor/value.js @@ -1,40 +1,40 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000' - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - radius: 10, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + radius: 10, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderColor/indexable.js b/test/fixtures/controller.line/pointBorderColor/indexable.js index 3f3298682e5..0b82fcc5d6c 100644 --- a/test/fixtures/controller.line/pointBorderColor/indexable.js +++ b/test/fixtures/controller.line/pointBorderColor/indexable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - radius: 10 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + radius: 10 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderColor/scriptable.js b/test/fixtures/controller.line/pointBorderColor/scriptable.js index 556be7c6484..29e338983e8 100644 --- a/test/fixtures/controller.line/pointBorderColor/scriptable.js +++ b/test/fixtures/controller.line/pointBorderColor/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 0 ? '#0000ff' - : value > -8 ? '#ff0000' - : '#00ff00'; - }, - radius: 10, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + }, + radius: 10, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderColor/value.js b/test/fixtures/controller.line/pointBorderColor/value.js index 1650b32c819..cd203c80e71 100644 --- a/test/fixtures/controller.line/pointBorderColor/value.js +++ b/test/fixtures/controller.line/pointBorderColor/value.js @@ -1,40 +1,40 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#ff0000' - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#00ff00', - radius: 10, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#ff0000' + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#00ff00', + radius: 10, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderWidth/indexable.js b/test/fixtures/controller.line/pointBorderWidth/indexable.js index 0e819b25590..2da75b83e39 100644 --- a/test/fixtures/controller.line/pointBorderWidth/indexable.js +++ b/test/fixtures/controller.line/pointBorderWidth/indexable.js @@ -1,46 +1,46 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#00ff00', - pointBorderWidth: [ - 1, 2, 3, 4, 5, 6 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - borderWidth: [ - 6, 5, 4, 3, 2, 1 - ], - radius: 10 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#00ff00', + pointBorderWidth: [ + 1, 2, 3, 4, 5, 6 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + borderWidth: [ + 6, 5, 4, 3, 2, 1 + ], + radius: 10 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderWidth/scriptable.js b/test/fixtures/controller.line/pointBorderWidth/scriptable.js index d833281b447..1e0b854ce93 100644 --- a/test/fixtures/controller.line/pointBorderWidth/scriptable.js +++ b/test/fixtures/controller.line/pointBorderWidth/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointBorderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 10 - : value > -4 ? 5 - : 2; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - borderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 2 - : value > -4 ? 5 - : 10; - }, - radius: 10, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointBorderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 10 + : value > -4 ? 5 + : 2; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 2 + : value > -4 ? 5 + : 10; + }, + radius: 10, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointBorderWidth/value.js b/test/fixtures/controller.line/pointBorderWidth/value.js index 1327fd3a52f..bad244c22d4 100644 --- a/test/fixtures/controller.line/pointBorderWidth/value.js +++ b/test/fixtures/controller.line/pointBorderWidth/value.js @@ -1,42 +1,42 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointBorderWidth: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#00ff00', - borderWidth: 3, - radius: 10, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointBorderWidth: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#00ff00', + borderWidth: 3, + radius: 10, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointStyle/indexable.js b/test/fixtures/controller.line/pointStyle/indexable.js index 3029f811654..852115c9a3a 100644 --- a/test/fixtures/controller.line/pointStyle/indexable.js +++ b/test/fixtures/controller.line/pointStyle/indexable.js @@ -1,58 +1,58 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000', - pointBorderColor: '#ff0000', - pointStyle: [ - 'circle', - 'cross', - 'crossRot', - 'dash', - 'line', - 'rect', - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - borderColor: '#00ff00', - pointStyle: [ - 'line', - 'rect', - 'rectRounded', - 'rectRot', - 'star', - 'triangle' - ], - radius: 10 - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000', + pointBorderColor: '#ff0000', + pointStyle: [ + 'circle', + 'cross', + 'crossRot', + 'dash', + 'line', + 'rect', + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + borderColor: '#00ff00', + pointStyle: [ + 'line', + 'rect', + 'rectRounded', + 'rectRot', + 'star', + 'triangle' + ], + radius: 10 + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointStyle/scriptable.js b/test/fixtures/controller.line/pointStyle/scriptable.js index bd68344e509..60d6b4ce157 100644 --- a/test/fixtures/controller.line/pointStyle/scriptable.js +++ b/test/fixtures/controller.line/pointStyle/scriptable.js @@ -1,59 +1,59 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000', - pointBorderColor: '#ff0000', - pointStyle: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? 'rect' - : value > 0 ? 'star' - : value > -8 ? 'cross' - : 'triangle'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#0000ff', - borderColor: '#0000ff', - pointStyle: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? 'triangle' - : value > 0 ? 'cross' - : value > -8 ? 'star' - : 'rect'; - }, - radius: 10, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000', + pointBorderColor: '#ff0000', + pointStyle: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? 'rect' + : value > 0 ? 'star' + : value > -8 ? 'cross' + : 'triangle'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#0000ff', + borderColor: '#0000ff', + pointStyle: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? 'triangle' + : value > 0 ? 'cross' + : value > -8 ? 'star' + : 'rect'; + }, + radius: 10, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/pointStyle/value.js b/test/fixtures/controller.line/pointStyle/value.js index 87f78d79d49..945c25b8bc1 100644 --- a/test/fixtures/controller.line/pointStyle/value.js +++ b/test/fixtures/controller.line/pointStyle/value.js @@ -1,42 +1,42 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#ff0000', - pointStyle: 'star', - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - pointStyle: 'rect', - radius: 10, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#ff0000', + pointStyle: 'star', + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + pointStyle: 'rect', + radius: 10, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/radius/indexable.js b/test/fixtures/controller.line/radius/indexable.js index 2c3ade90ccd..7024db7cb49 100644 --- a/test/fixtures/controller.line/radius/indexable.js +++ b/test/fixtures/controller.line/radius/indexable.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#00ff00', - pointRadius: [ - 1, 2, 3, 4, 5, 6 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#ff0000', - radius: [ - 6, 5, 4, 3, 2, 1 - ], - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#00ff00', + pointRadius: [ + 1, 2, 3, 4, 5, 6 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#ff0000', + radius: [ + 6, 5, 4, 3, 2, 1 + ], + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/radius/scriptable.js b/test/fixtures/controller.line/radius/scriptable.js index e08e919b028..2d524999ef0 100644 --- a/test/fixtures/controller.line/radius/scriptable.js +++ b/test/fixtures/controller.line/radius/scriptable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#0000ff', - pointRadius: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 10 - : value > -4 ? 5 - : 2; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#ff0000', - radius: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 2 - : value > -4 ? 5 - : 10; - }, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#0000ff', + pointRadius: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 10 + : value > -4 ? 5 + : 2; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#ff0000', + radius: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 2 + : value > -4 ? 5 + : 10; + }, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/radius/value.js b/test/fixtures/controller.line/radius/value.js index 60a33c69bf5..fccbe7fc728 100644 --- a/test/fixtures/controller.line/radius/value.js +++ b/test/fixtures/controller.line/radius/value.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#0000ff', - pointRadius: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - radius: 3, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#0000ff', + pointRadius: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + radius: 3, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/rotation/indexable.js b/test/fixtures/controller.line/rotation/indexable.js index d3f33a05da6..6c7a86d8e81 100644 --- a/test/fixtures/controller.line/rotation/indexable.js +++ b/test/fixtures/controller.line/rotation/indexable.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#00ff00', - pointRotation: [ - 0, 30, 60, 90, 120, 150 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - borderWidth: 10, - pointStyle: 'line', - rotation: [ - 150, 120, 90, 60, 30, 0 - ], - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#00ff00', + pointRotation: [ + 0, 30, 60, 90, 120, 150 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + borderWidth: 10, + pointStyle: 'line', + rotation: [ + 150, 120, 90, 60, 30, 0 + ], + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/rotation/scriptable.js b/test/fixtures/controller.line/rotation/scriptable.js index 9cb5199d6ee..d22b9d13b95 100644 --- a/test/fixtures/controller.line/rotation/scriptable.js +++ b/test/fixtures/controller.line/rotation/scriptable.js @@ -1,56 +1,56 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointRotation: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 120 - : value > -4 ? 60 - : 0; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - rotation: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 0 - : value > -4 ? 60 - : 120; - }, - pointStyle: 'line', - radius: 10, - } - }, - scales: { - x: {display: false}, - y: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointRotation: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 120 + : value > -4 ? 60 + : 0; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + rotation: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 0 + : value > -4 ? 60 + : 120; + }, + pointStyle: 'line', + radius: 10, + } + }, + scales: { + x: {display: false}, + y: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/rotation/value.js b/test/fixtures/controller.line/rotation/value.js index 00072267a61..d96e37d2682 100644 --- a/test/fixtures/controller.line/rotation/value.js +++ b/test/fixtures/controller.line/rotation/value.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointRotation: 90 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#00ff00', - pointStyle: 'line', - radius: 10, - rotation: 0, - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointRotation: 90 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#00ff00', + pointStyle: 'line', + radius: 10, + rotation: 0, + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/showLine/dataset.js b/test/fixtures/controller.line/showLine/dataset.js index 2eee50599e8..d8553d6086b 100644 --- a/test/fixtures/controller.line/showLine/dataset.js +++ b/test/fixtures/controller.line/showLine/dataset.js @@ -1,40 +1,40 @@ module.exports = { - description: 'should draw all elements except lines turned off per dataset', - config: { - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1', - borderColor: 'red', - backgroundColor: 'green', - showLine: false, - fill: false - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - scales: { - x: { - display: false - }, - y: { - display: false - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - width: 512, - height: 512 - } - } + description: 'should draw all elements except lines turned off per dataset', + config: { + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + borderColor: 'red', + backgroundColor: 'green', + showLine: false, + fill: false + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + scales: { + x: { + display: false + }, + y: { + display: false + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + width: 512, + height: 512 + } + } }; diff --git a/test/fixtures/controller.line/showLine/false.js b/test/fixtures/controller.line/showLine/false.js index 648ee8d06e2..2457c2a8b84 100644 --- a/test/fixtures/controller.line/showLine/false.js +++ b/test/fixtures/controller.line/showLine/false.js @@ -1,33 +1,33 @@ module.exports = { - description: 'should draw all elements except lines', - config: { - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1', - borderColor: 'red', - backgroundColor: 'green', - fill: true - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: false, - scales: { - x: { - display: false - }, - y: { - display: false - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - } + description: 'should draw all elements except lines', + config: { + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + borderColor: 'red', + backgroundColor: 'green', + fill: true + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: false, + scales: { + x: { + display: false + }, + y: { + display: false + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + } }; diff --git a/test/fixtures/controller.line/stacking/order-default.js b/test/fixtures/controller.line/stacking/order-default.js index 073afcda5b6..18c694f7521 100644 --- a/test/fixtures/controller.line/stacking/order-default.js +++ b/test/fixtures/controller.line/stacking/order-default.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [3, 1, 2, 0, 8, 1], - backgroundColor: '#ff0000' - }, - { - // option in element (fallback) - data: [0, 4, 2, 6, 4, 8], - backgroundColor: '#00ff00' - } - ] - }, - options: { - elements: { - line: { - fill: true - }, - point: { - radius: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {stacked: true, display: false}, - y: {stacked: true, display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00' + } + ] + }, + options: { + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {stacked: true, display: false}, + y: {stacked: true, display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/stacking/order-specified.js b/test/fixtures/controller.line/stacking/order-specified.js index 811496e3053..262988d8a37 100644 --- a/test/fixtures/controller.line/stacking/order-specified.js +++ b/test/fixtures/controller.line/stacking/order-specified.js @@ -1,51 +1,51 @@ module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [3, 1, 2, 0, 8, 1], - backgroundColor: '#ff0000', - order: 2 - }, - { - // option in element (fallback) - data: [0, 4, 2, 6, 4, 8], - backgroundColor: '#00ff00', - order: 1 - } - ] - }, - options: { - elements: { - line: { - fill: true - }, - point: { - radius: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - x: {stacked: true, display: false}, - y: {stacked: true, display: false} - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000', + order: 2 + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00', + order: 1 + } + ] + }, + options: { + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + x: {stacked: true, display: false}, + y: {stacked: true, display: false} + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.line/stacking/stacked-scatter.js b/test/fixtures/controller.line/stacking/stacked-scatter.js index a8085b629f3..00da2a77bd5 100644 --- a/test/fixtures/controller.line/stacking/stacked-scatter.js +++ b/test/fixtures/controller.line/stacking/stacked-scatter.js @@ -1,74 +1,74 @@ module.exports = { - config: { - type: 'scatter', - data: { - datasets: [{ - label: 'label1', - data: [{ - x: 0, - y: 30 - }, { - x: 5, - y: 35 - }, { - x: 10, - y: 20 - }], - backgroundColor: '#42A8E4' - }, - { - label: 'label2', - data: [{ - x: 0, - y: 10 - }, { - x: 5, - y: 15 - }, { - x: 10, - y: 15 - }], - backgroundColor: '#FC3F55' - }, - { - label: 'label3', - data: [{ - x: 0, - y: -15 - }, { - x: 5, - y: -10 - }, { - x: 10, - y: -20 - }], - backgroundColor: '#FFBE3F' - }], - }, - options: { - scales: { - x: { - display: false, - position: 'bottom', - }, - y: { - stacked: true, - display: false, - position: 'left', - }, - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - }, - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'scatter', + data: { + datasets: [{ + label: 'label1', + data: [{ + x: 0, + y: 30 + }, { + x: 5, + y: 35 + }, { + x: 10, + y: 20 + }], + backgroundColor: '#42A8E4' + }, + { + label: 'label2', + data: [{ + x: 0, + y: 10 + }, { + x: 5, + y: 15 + }, { + x: 10, + y: 15 + }], + backgroundColor: '#FC3F55' + }, + { + label: 'label3', + data: [{ + x: 0, + y: -15 + }, { + x: 5, + y: -10 + }, { + x: 10, + y: -20 + }], + backgroundColor: '#FFBE3F' + }], + }, + options: { + scales: { + x: { + display: false, + position: 'bottom', + }, + y: { + stacked: true, + display: false, + position: 'left', + }, + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + }, + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/indexable-dataset.js b/test/fixtures/controller.polarArea/backgroundColor/indexable-dataset.js index ab4f654fe37..1b92d096bae 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/indexable-dataset.js +++ b/test/fixtures/controller.polarArea/backgroundColor/indexable-dataset.js @@ -1,35 +1,35 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - ] - }, - options: { - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + ] + }, + options: { + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/indexable-element-options.js b/test/fixtures/controller.polarArea/backgroundColor/indexable-element-options.js index 813d18274f2..fb3d4f42ce4 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/indexable-element-options.js +++ b/test/fixtures/controller.polarArea/backgroundColor/indexable-element-options.js @@ -1,39 +1,39 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ] - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ] + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/scriptable-dataset.js b/test/fixtures/controller.polarArea/backgroundColor/scriptable-dataset.js index e3754faf45f..8d11108aadf 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/scriptable-dataset.js +++ b/test/fixtures/controller.polarArea/backgroundColor/scriptable-dataset.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - }, - ] - }, - options: { - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + }, + ] + }, + options: { + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/scriptable-element-options.js b/test/fixtures/controller.polarArea/backgroundColor/scriptable-element-options.js index dd9651c4c6f..36415025c2c 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/scriptable-element-options.js +++ b/test/fixtures/controller.polarArea/backgroundColor/scriptable-element-options.js @@ -1,38 +1,38 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/value-dataset.js b/test/fixtures/controller.polarArea/backgroundColor/value-dataset.js index 5fe96ce475e..b9be824a6f0 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/value-dataset.js +++ b/test/fixtures/controller.polarArea/backgroundColor/value-dataset.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - backgroundColor: '#ff0000' - }, - ] - }, - options: { - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + backgroundColor: '#ff0000' + }, + ] + }, + options: { + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/backgroundColor/value-element-options.js b/test/fixtures/controller.polarArea/backgroundColor/value-element-options.js index 1b6fef8e62e..f68b70dbf09 100644 --- a/test/fixtures/controller.polarArea/backgroundColor/value-element-options.js +++ b/test/fixtures/controller.polarArea/backgroundColor/value-element-options.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: '#00ff00' - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: '#00ff00' + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/indexable-dataset.js b/test/fixtures/controller.polarArea/borderAlign/indexable-dataset.js index 5f121da5bd5..99b285d385f 100644 --- a/test/fixtures/controller.polarArea/borderAlign/indexable-dataset.js +++ b/test/fixtures/controller.polarArea/borderAlign/indexable-dataset.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: [ - 'center', - 'inner', - 'center', - 'inner', - 'center', - 'inner', - ], - borderColor: '#00ff00' - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff0000', - borderWidth: 5, - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: [ + 'center', + 'inner', + 'center', + 'inner', + 'center', + 'inner', + ], + borderColor: '#00ff00' + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff0000', + borderWidth: 5, + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/indexable-element-options.js b/test/fixtures/controller.polarArea/borderAlign/indexable-element-options.js index 8a3cc5c94e8..fcdb698d47d 100644 --- a/test/fixtures/controller.polarArea/borderAlign/indexable-element-options.js +++ b/test/fixtures/controller.polarArea/borderAlign/indexable-element-options.js @@ -1,42 +1,42 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff0000', - borderWidth: 5, - borderAlign: [ - 'center', - 'inner', - 'center', - 'inner', - 'center', - 'inner', - ] - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff0000', + borderWidth: 5, + borderAlign: [ + 'center', + 'inner', + 'center', + 'inner', + 'center', + 'inner', + ] + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/scriptable-dataset.js b/test/fixtures/controller.polarArea/borderAlign/scriptable-dataset.js index d86b4a924b0..6689e0841ac 100644 --- a/test/fixtures/controller.polarArea/borderAlign/scriptable-dataset.js +++ b/test/fixtures/controller.polarArea/borderAlign/scriptable-dataset.js @@ -1,39 +1,39 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 'inner' : 'center'; - }, - borderColor: '#0000ff', - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff00ff', - borderWidth: 8, - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 'inner' : 'center'; + }, + borderColor: '#0000ff', + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff00ff', + borderWidth: 8, + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/scriptable-element-options.js b/test/fixtures/controller.polarArea/borderAlign/scriptable-element-options.js index 1cc9f41cea5..4528cad8dc6 100644 --- a/test/fixtures/controller.polarArea/borderAlign/scriptable-element-options.js +++ b/test/fixtures/controller.polarArea/borderAlign/scriptable-element-options.js @@ -1,38 +1,38 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#ff00ff', - borderWidth: 8, - borderAlign: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 'center' : 'inner'; - } - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#ff00ff', + borderWidth: 8, + borderAlign: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 'center' : 'inner'; + } + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/value-dataset.js b/test/fixtures/controller.polarArea/borderAlign/value-dataset.js index 08c1a27b1a3..c201cbfd989 100644 --- a/test/fixtures/controller.polarArea/borderAlign/value-dataset.js +++ b/test/fixtures/controller.polarArea/borderAlign/value-dataset.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderAlign: 'inner', - borderColor: '#00ff00', - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#0000ff', - borderWidth: 4, - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderAlign: 'inner', + borderColor: '#00ff00', + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#0000ff', + borderWidth: 4, + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderAlign/value-element-options.js b/test/fixtures/controller.polarArea/borderAlign/value-element-options.js index fff5d37e42a..1f3ff0632e0 100644 --- a/test/fixtures/controller.polarArea/borderAlign/value-element-options.js +++ b/test/fixtures/controller.polarArea/borderAlign/value-element-options.js @@ -1,35 +1,35 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderAlign: 'center', - borderColor: '#0000ff', - borderWidth: 4, - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderAlign: 'center', + borderColor: '#0000ff', + borderWidth: 4, + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/indexable-dataset.js b/test/fixtures/controller.polarArea/borderColor/indexable-dataset.js index a9b8a49eed7..6d74af80c9c 100644 --- a/test/fixtures/controller.polarArea/borderColor/indexable-dataset.js +++ b/test/fixtures/controller.polarArea/borderColor/indexable-dataset.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/indexable-element-options.js b/test/fixtures/controller.polarArea/borderColor/indexable-element-options.js index 6a02f6b4d24..550ff4c6fc7 100644 --- a/test/fixtures/controller.polarArea/borderColor/indexable-element-options.js +++ b/test/fixtures/controller.polarArea/borderColor/indexable-element-options.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/scriptable-dataset.js b/test/fixtures/controller.polarArea/borderColor/scriptable-dataset.js index e5639434643..376cd4b951a 100644 --- a/test/fixtures/controller.polarArea/borderColor/scriptable-dataset.js +++ b/test/fixtures/controller.polarArea/borderColor/scriptable-dataset.js @@ -1,40 +1,40 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 6 ? '#00ff00' - : value > 2 ? '#0000ff' - : '#ff00ff'; - } - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 6 ? '#00ff00' + : value > 2 ? '#0000ff' + : '#ff00ff'; + } + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/scriptable-element-options.js b/test/fixtures/controller.polarArea/borderColor/scriptable-element-options.js index 733b21933ba..985e868cca6 100644 --- a/test/fixtures/controller.polarArea/borderColor/scriptable-element-options.js +++ b/test/fixtures/controller.polarArea/borderColor/scriptable-element-options.js @@ -1,40 +1,40 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 6 ? '#0000ff' - : value > 2 ? '#ff0000' - : '#00ff00'; - }, - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 6 ? '#0000ff' + : value > 2 ? '#ff0000' + : '#00ff00'; + }, + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/value-dataset.js b/test/fixtures/controller.polarArea/borderColor/value-dataset.js index 925eddf2e7a..d2d40f0c4dd 100644 --- a/test/fixtures/controller.polarArea/borderColor/value-dataset.js +++ b/test/fixtures/controller.polarArea/borderColor/value-dataset.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderColor: '#ff0000' - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderColor: '#ff0000' + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderColor/value-element-options.js b/test/fixtures/controller.polarArea/borderColor/value-element-options.js index b75ea24311f..63b106aba93 100644 --- a/test/fixtures/controller.polarArea/borderColor/value-element-options.js +++ b/test/fixtures/controller.polarArea/borderColor/value-element-options.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#00ff00', - borderWidth: 8 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#00ff00', + borderWidth: 8 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/indexable-dataset.js b/test/fixtures/controller.polarArea/borderWidth/indexable-dataset.js index 901612e1bc6..e968c2cd916 100644 --- a/test/fixtures/controller.polarArea/borderWidth/indexable-dataset.js +++ b/test/fixtures/controller.polarArea/borderWidth/indexable-dataset.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: [ - 0, - 1, - 2, - 3, - 4, - 5 - ] - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/indexable-element-options.js b/test/fixtures/controller.polarArea/borderWidth/indexable-element-options.js index 8b75cc985bf..c18ce319546 100644 --- a/test/fixtures/controller.polarArea/borderWidth/indexable-element-options.js +++ b/test/fixtures/controller.polarArea/borderWidth/indexable-element-options.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: [ - 5, - 4, - 3, - 2, - 1, - 0 - ] - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: [ + 5, + 4, + 3, + 2, + 1, + 0 + ] + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/scriptable-dataset.js b/test/fixtures/controller.polarArea/borderWidth/scriptable-dataset.js index bdd4b4e3204..72801329928 100644 --- a/test/fixtures/controller.polarArea/borderWidth/scriptable-dataset.js +++ b/test/fixtures/controller.polarArea/borderWidth/scriptable-dataset.js @@ -1,37 +1,37 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return Math.abs(value); - } - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return Math.abs(value); + } + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/scriptable-element-options.js b/test/fixtures/controller.polarArea/borderWidth/scriptable-element-options.js index f57915eb827..3bc685715be 100644 --- a/test/fixtures/controller.polarArea/borderWidth/scriptable-element-options.js +++ b/test/fixtures/controller.polarArea/borderWidth/scriptable-element-options.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: function(ctx) { - return ctx.dataIndex * 2; - } - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: function(ctx) { + return ctx.dataIndex * 2; + } + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/value-dataset.js b/test/fixtures/controller.polarArea/borderWidth/value-dataset.js index 0981b4e2bee..019fa73c0f9 100644 --- a/test/fixtures/controller.polarArea/borderWidth/value-dataset.js +++ b/test/fixtures/controller.polarArea/borderWidth/value-dataset.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 2, 4, null, 6, 8], - borderWidth: 2 - }, - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 2, 4, null, 6, 8], + borderWidth: 2 + }, + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.polarArea/borderWidth/value-element-options.js b/test/fixtures/controller.polarArea/borderWidth/value-element-options.js index 42d2a86be51..ce13ac48dea 100644 --- a/test/fixtures/controller.polarArea/borderWidth/value-element-options.js +++ b/test/fixtures/controller.polarArea/borderWidth/value-element-options.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'polarArea', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in element (fallback) - data: [0, 2, 4, null, 6, 8], - } - ] - }, - options: { - elements: { - arc: { - backgroundColor: 'transparent', - borderColor: '#888', - borderWidth: 4 - } - }, - scales: { - r: { - display: false - } - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in element (fallback) + data: [0, 2, 4, null, 6, 8], + } + ] + }, + options: { + elements: { + arc: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: 4 + } + }, + scales: { + r: { + display: false + } + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/backgroundColor/scriptable.js b/test/fixtures/controller.radar/backgroundColor/scriptable.js index 54ccb5f1f27..8183eacb39f 100644 --- a/test/fixtures/controller.radar/backgroundColor/scriptable.js +++ b/test/fixtures/controller.radar/backgroundColor/scriptable.js @@ -1,59 +1,59 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - backgroundColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#ff00ff'; - }, - fill: true, - }, - point: { - backgroundColor: '#0000ff', - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15, - }, - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + backgroundColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#ff00ff'; + }, + fill: true, + }, + point: { + backgroundColor: '#0000ff', + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15, + }, + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/backgroundColor/value.js b/test/fixtures/controller.radar/backgroundColor/value.js index 0c60841047d..6922182c66d 100644 --- a/test/fixtures/controller.radar/backgroundColor/value.js +++ b/test/fixtures/controller.radar/backgroundColor/value.js @@ -1,48 +1,48 @@ module.exports = { - config: { - type: 'radar', - 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: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - backgroundColor: '#00ff00', - fill: true, - }, - point: { - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + 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: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + backgroundColor: '#00ff00', + fill: true, + }, + point: { + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderCapStyle/scriptable.js b/test/fixtures/controller.radar/borderCapStyle/scriptable.js index 0bd84298bbd..420e994207d 100644 --- a/test/fixtures/controller.radar/borderCapStyle/scriptable.js +++ b/test/fixtures/controller.radar/borderCapStyle/scriptable.js @@ -1,61 +1,61 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2], - datasets: [ - { - // option in dataset - data: [null, 3, 3], - borderCapStyle: function(ctx) { - var index = (ctx.datasetIndex % 2); - return index === 0 ? 'round' - : index === 1 ? 'square' - : 'butt'; - } - }, - { - // option in element (fallback) - data: [null, 2, 2] - }, - { - // option in element (fallback) - data: [null, 1, 1] - } - ] - }, - options: { - elements: { - line: { - borderCapStyle: function(ctx) { - var index = (ctx.datasetIndex % 3); - return index === 0 ? 'round' - : index === 1 ? 'square' - : 'butt'; - }, - borderColor: '#ff0000', - borderWidth: 32, - fill: false - }, - point: { - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2], + datasets: [ + { + // option in dataset + data: [null, 3, 3], + borderCapStyle: function(ctx) { + var index = (ctx.datasetIndex % 2); + return index === 0 ? 'round' + : index === 1 ? 'square' + : 'butt'; + } + }, + { + // option in element (fallback) + data: [null, 2, 2] + }, + { + // option in element (fallback) + data: [null, 1, 1] + } + ] + }, + options: { + elements: { + line: { + borderCapStyle: function(ctx) { + var index = (ctx.datasetIndex % 3); + return index === 0 ? 'round' + : index === 1 ? 'square' + : 'butt'; + }, + borderColor: '#ff0000', + borderWidth: 32, + fill: false + }, + point: { + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderCapStyle/value.js b/test/fixtures/controller.radar/borderCapStyle/value.js index c5b914f9ec4..72fdcab1759 100644 --- a/test/fixtures/controller.radar/borderCapStyle/value.js +++ b/test/fixtures/controller.radar/borderCapStyle/value.js @@ -1,52 +1,52 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2], - datasets: [ - { - // option in dataset - data: [null, 3, 3], - borderCapStyle: 'round' - }, - { - // option in dataset - data: [null, 2, 2], - borderCapStyle: 'square' - }, - { - // option in element (fallback) - data: [null, 1, 1] - } - ] - }, - options: { - elements: { - line: { - borderCapStyle: 'butt', - borderColor: '#00ff00', - borderWidth: 32, - fill: false - }, - point: { - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2], + datasets: [ + { + // option in dataset + data: [null, 3, 3], + borderCapStyle: 'round' + }, + { + // option in dataset + data: [null, 2, 2], + borderCapStyle: 'square' + }, + { + // option in element (fallback) + data: [null, 1, 1] + } + ] + }, + options: { + elements: { + line: { + borderCapStyle: 'butt', + borderColor: '#00ff00', + borderWidth: 32, + fill: false + }, + point: { + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderColor/scriptable.js b/test/fixtures/controller.radar/borderColor/scriptable.js index b301e2e9efe..14fa9fcb6b6 100644 --- a/test/fixtures/controller.radar/borderColor/scriptable.js +++ b/test/fixtures/controller.radar/borderColor/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#0000ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - borderColor: function(ctx) { - var index = ctx.index; - return index === 0 ? '#ff0000' - : index === 1 ? '#00ff00' - : '#0000ff'; - }, - borderWidth: 10, - fill: false - }, - point: { - borderColor: '#ff0000', - borderWidth: 10, - radius: 16 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#0000ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + borderColor: function(ctx) { + var index = ctx.index; + return index === 0 ? '#ff0000' + : index === 1 ? '#00ff00' + : '#0000ff'; + }, + borderWidth: 10, + fill: false + }, + point: { + borderColor: '#ff0000', + borderWidth: 10, + radius: 16 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderColor/value.js b/test/fixtures/controller.radar/borderColor/value.js index ce40c1d4f7b..5e130c29df6 100644 --- a/test/fixtures/controller.radar/borderColor/value.js +++ b/test/fixtures/controller.radar/borderColor/value.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'radar', - 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: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#0000ff', - fill: false - }, - point: { - borderColor: '#0000ff', - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + 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: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#0000ff', + fill: false + }, + point: { + borderColor: '#0000ff', + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderDash/scriptable.js b/test/fixtures/controller.radar/borderDash/scriptable.js index a26873c1136..de31f9ddf9c 100644 --- a/test/fixtures/controller.radar/borderDash/scriptable.js +++ b/test/fixtures/controller.radar/borderDash/scriptable.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderDash: function(ctx) { - return ctx.datasetIndex === 0 ? [5] : [10]; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: function(ctx) { - return ctx.datasetIndex === 0 ? [5] : [10]; - }, - fill: true, - }, - point: { - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderDash: function(ctx) { + return ctx.datasetIndex === 0 ? [5] : [10]; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: function(ctx) { + return ctx.datasetIndex === 0 ? [5] : [10]; + }, + fill: true, + }, + point: { + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderDash/value.js b/test/fixtures/controller.radar/borderDash/value.js index 3d19af89839..933b8b36a84 100644 --- a/test/fixtures/controller.radar/borderDash/value.js +++ b/test/fixtures/controller.radar/borderDash/value.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: '#ff0000', - borderDash: [5] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [10], - fill: false - }, - point: { - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#ff0000', + borderDash: [5] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [10], + fill: false + }, + point: { + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderDashOffset/scriptable.js b/test/fixtures/controller.radar/borderDashOffset/scriptable.js index 0aecff6d409..4de8f892523 100644 --- a/test/fixtures/controller.radar/borderDashOffset/scriptable.js +++ b/test/fixtures/controller.radar/borderDashOffset/scriptable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [1, 1, 1, 1], - borderColor: '#ff0000', - borderDash: [20], - borderDashOffset: function(ctx) { - return ctx.datasetIndex === 0 ? 5.0 : 0.0; - } - }, - { - // option in element (fallback) - data: [0, 0, 0, 0] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [20], - borderDashOffset: function(ctx) { - return ctx.datasetIndex === 0 ? 5.0 : 0.0; - }, - fill: false - }, - point: { - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - min: -1 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [1, 1, 1, 1], + borderColor: '#ff0000', + borderDash: [20], + borderDashOffset: function(ctx) { + return ctx.datasetIndex === 0 ? 5.0 : 0.0; + } + }, + { + // option in element (fallback) + data: [0, 0, 0, 0] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [20], + borderDashOffset: function(ctx) { + return ctx.datasetIndex === 0 ? 5.0 : 0.0; + }, + fill: false + }, + point: { + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + min: -1 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderDashOffset/value.js b/test/fixtures/controller.radar/borderDashOffset/value.js index 18d0bcc4783..d24cc4ff15d 100644 --- a/test/fixtures/controller.radar/borderDashOffset/value.js +++ b/test/fixtures/controller.radar/borderDashOffset/value.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [1, 1, 1, 1, 1, 1], - borderColor: '#ff0000', - borderDash: [20], - borderDashOffset: 5.0 - }, - { - // option in element (fallback) - data: [0, 0, 0, 0, 0, 0] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderDash: [20], - borderDashOffset: 0.0, // default - fill: false - }, - point: { - radius: 10 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - min: -1 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [1, 1, 1, 1, 1, 1], + borderColor: '#ff0000', + borderDash: [20], + borderDashOffset: 5.0 + }, + { + // option in element (fallback) + data: [0, 0, 0, 0, 0, 0] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderDash: [20], + borderDashOffset: 0.0, // default + fill: false + }, + point: { + radius: 10 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + min: -1 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderJoinStyle/scriptable.js b/test/fixtures/controller.radar/borderJoinStyle/scriptable.js index d36a3a05035..a1d73c48c2c 100644 --- a/test/fixtures/controller.radar/borderJoinStyle/scriptable.js +++ b/test/fixtures/controller.radar/borderJoinStyle/scriptable.js @@ -1,61 +1,61 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [3, 3, null, 3], - borderColor: '#ff0000', - borderJoinStyle: function(ctx) { - var index = ctx.datasetIndex % 3; - return index === 0 ? 'round' - : index === 1 ? 'miter' - : 'bevel'; - } - }, - { - // option in element (fallback) - data: [2, 2, null, 2], - borderColor: '#0000ff' - }, - { - // option in element (fallback) - data: [1, 1, null, 1] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderJoinStyle: function(ctx) { - var index = (ctx.datasetIndex % 3); - return index === 0 ? 'round' - : index === 1 ? 'miter' - : 'bevel'; - }, - borderWidth: 25, - fill: false, - tension: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [3, 3, null, 3], + borderColor: '#ff0000', + borderJoinStyle: function(ctx) { + var index = ctx.datasetIndex % 3; + return index === 0 ? 'round' + : index === 1 ? 'miter' + : 'bevel'; + } + }, + { + // option in element (fallback) + data: [2, 2, null, 2], + borderColor: '#0000ff' + }, + { + // option in element (fallback) + data: [1, 1, null, 1] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderJoinStyle: function(ctx) { + var index = (ctx.datasetIndex % 3); + return index === 0 ? 'round' + : index === 1 ? 'miter' + : 'bevel'; + }, + borderWidth: 25, + fill: false, + tension: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderJoinStyle/value.js b/test/fixtures/controller.radar/borderJoinStyle/value.js index fc05fb2515a..669fa561e10 100644 --- a/test/fixtures/controller.radar/borderJoinStyle/value.js +++ b/test/fixtures/controller.radar/borderJoinStyle/value.js @@ -1,52 +1,52 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3], - datasets: [ - { - // option in dataset - data: [3, 3, null, 3], - borderColor: '#ff0000', - borderJoinStyle: 'round' - }, - { - // option in element (fallback) - data: [2, 2, null, 2], - borderColor: '#0000ff', - borderJoinStyle: 'bevel' - }, - { - // option in element (fallback) - data: [1, 1, null, 1] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderJoinStyle: 'miter', - borderWidth: 25, - fill: false, - tension: 0 - } - }, - layout: { - padding: 32 - }, - scales: { - r: { - display: false, - beginAtZero: true - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3], + datasets: [ + { + // option in dataset + data: [3, 3, null, 3], + borderColor: '#ff0000', + borderJoinStyle: 'round' + }, + { + // option in element (fallback) + data: [2, 2, null, 2], + borderColor: '#0000ff', + borderJoinStyle: 'bevel' + }, + { + // option in element (fallback) + data: [1, 1, null, 1] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderJoinStyle: 'miter', + borderWidth: 25, + fill: false, + tension: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + r: { + display: false, + beginAtZero: true + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderWidth/scriptable.js b/test/fixtures/controller.radar/borderWidth/scriptable.js index dc0a8e0cf5f..2426ea2f127 100644 --- a/test/fixtures/controller.radar/borderWidth/scriptable.js +++ b/test/fixtures/controller.radar/borderWidth/scriptable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: '#0000ff', - borderWidth: function(ctx) { - var index = ctx.index; - return index % 2 ? 10 : 20; - }, - pointBorderColor: '#00ff00' - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#ff0000', - borderWidth: function(ctx) { - var index = ctx.index; - return index % 2 ? 10 : 20; - }, - fill: false - }, - point: { - borderColor: '#00ff00', - borderWidth: 5, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#0000ff', + borderWidth: function(ctx) { + var index = ctx.index; + return index % 2 ? 10 : 20; + }, + pointBorderColor: '#00ff00' + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#ff0000', + borderWidth: function(ctx) { + var index = ctx.index; + return index % 2 ? 10 : 20; + }, + fill: false + }, + point: { + borderColor: '#00ff00', + borderWidth: 5, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderWidth/value.js b/test/fixtures/controller.radar/borderWidth/value.js index 91880c36eb5..58ee10d23bf 100644 --- a/test/fixtures/controller.radar/borderWidth/value.js +++ b/test/fixtures/controller.radar/borderWidth/value.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - borderColor: '#0000ff', - borderWidth: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 3, - fill: false - }, - point: { - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#0000ff', + borderWidth: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 3, + fill: false + }, + point: { + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/borderWidth/zero.js b/test/fixtures/controller.radar/borderWidth/zero.js index c7110b9be74..a1c8a8c618d 100644 --- a/test/fixtures/controller.radar/borderWidth/zero.js +++ b/test/fixtures/controller.radar/borderWidth/zero.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: '#0000ff', - borderColor: '#0000ff', - borderWidth: 0, - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - borderColor: '#00ff00', - borderWidth: 1, - fill: false - }, - point: { - backgroundColor: '#00ff00', - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#0000ff', + borderColor: '#0000ff', + borderWidth: 0, + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + borderColor: '#00ff00', + borderWidth: 1, + fill: false + }, + point: { + backgroundColor: '#00ff00', + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/fill/scriptable.js b/test/fixtures/controller.radar/fill/scriptable.js index b8d42b3ad78..7ef108dea86 100644 --- a/test/fixtures/controller.radar/fill/scriptable.js +++ b/test/fixtures/controller.radar/fill/scriptable.js @@ -1,50 +1,50 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: '#ff0000', - fill: function(ctx) { - return ctx.datasetIndex === 0 ? true : false; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - backgroundColor: '#00ff00', - fill: function(ctx) { - return ctx.datasetIndex === 0 ? true : false; - } - } - }, - scales: { - r: { - display: false, - min: -15 - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#ff0000', + fill: function(ctx) { + return ctx.datasetIndex === 0 ? true : false; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + backgroundColor: '#00ff00', + fill: function(ctx) { + return ctx.datasetIndex === 0 ? true : false; + } + } + }, + scales: { + r: { + display: false, + min: -15 + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/fill/value.js b/test/fixtures/controller.radar/fill/value.js index 8f72f59d92c..55fe8e197aa 100644 --- a/test/fixtures/controller.radar/fill/value.js +++ b/test/fixtures/controller.radar/fill/value.js @@ -1,46 +1,46 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: '#ff0000', - fill: false - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - backgroundColor: '#00ff00', - fill: true - } - }, - scales: { - r: { - display: false, - min: -15 - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#ff0000', + fill: false + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + backgroundColor: '#00ff00', + fill: true + } + }, + scales: { + r: { + display: false, + min: -15 + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBackgroundColor/indexable.js b/test/fixtures/controller.radar/pointBackgroundColor/indexable.js index 2bfb94caf40..2e456f6a2ec 100644 --- a/test/fixtures/controller.radar/pointBackgroundColor/indexable.js +++ b/test/fixtures/controller.radar/pointBackgroundColor/indexable.js @@ -1,56 +1,56 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - backgroundColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBackgroundColor/scriptable.js b/test/fixtures/controller.radar/pointBackgroundColor/scriptable.js index 7adbd97f04e..91b687cf6b2 100644 --- a/test/fixtures/controller.radar/pointBackgroundColor/scriptable.js +++ b/test/fixtures/controller.radar/pointBackgroundColor/scriptable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 0 ? '#0000ff' - : value > -8 ? '#ff0000' - : '#00ff00'; - }, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + }, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBackgroundColor/value.js b/test/fixtures/controller.radar/pointBackgroundColor/value.js index 66e1d416e60..b10722e6013 100644 --- a/test/fixtures/controller.radar/pointBackgroundColor/value.js +++ b/test/fixtures/controller.radar/pointBackgroundColor/value.js @@ -1,42 +1,42 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000' - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - backgroundColor: '#00ff00', - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + backgroundColor: '#00ff00', + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderColor/indexable.js b/test/fixtures/controller.radar/pointBorderColor/indexable.js index 13351597872..0b16c30177a 100644 --- a/test/fixtures/controller.radar/pointBorderColor/indexable.js +++ b/test/fixtures/controller.radar/pointBorderColor/indexable.js @@ -1,57 +1,57 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: [ - '#ff0000', - '#00ff00', - '#0000ff', - '#ffff00', - '#ff00ff', - '#000000' - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: [ - '#ff88ff', - '#888888', - '#ff8800', - '#00ff88', - '#8800ff', - '#ffff88' - ], - borderWidth: 5, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + borderWidth: 5, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderColor/scriptable.js b/test/fixtures/controller.radar/pointBorderColor/scriptable.js index 9e0cf8b8912..dcf5f75bf80 100644 --- a/test/fixtures/controller.radar/pointBorderColor/scriptable.js +++ b/test/fixtures/controller.radar/pointBorderColor/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff0000' - : value > 0 ? '#00ff00' - : value > -8 ? '#0000ff' - : '#ff00ff'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? '#ff00ff' - : value > 0 ? '#0000ff' - : value > -8 ? '#ff0000' - : '#00ff00'; - }, - borderWidth: 5, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + }, + borderWidth: 5, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderColor/value.js b/test/fixtures/controller.radar/pointBorderColor/value.js index ef4fdbebf67..a986abdf522 100644 --- a/test/fixtures/controller.radar/pointBorderColor/value.js +++ b/test/fixtures/controller.radar/pointBorderColor/value.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#ff0000' - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: '#00ff00', - borderWidth: 5, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#ff0000' + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: '#00ff00', + borderWidth: 5, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderWidth/indexable.js b/test/fixtures/controller.radar/pointBorderWidth/indexable.js index 7accea2d0f5..a8e8838e1bc 100644 --- a/test/fixtures/controller.radar/pointBorderWidth/indexable.js +++ b/test/fixtures/controller.radar/pointBorderWidth/indexable.js @@ -1,48 +1,48 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#00ff00', - pointBorderWidth: [ - 1, 2, 3, 4, 5, 6 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: '#ff0000', - borderWidth: [ - 6, 5, 4, 3, 2, 1 - ], - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#00ff00', + pointBorderWidth: [ + 1, 2, 3, 4, 5, 6 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: '#ff0000', + borderWidth: [ + 6, 5, 4, 3, 2, 1 + ], + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderWidth/scriptable.js b/test/fixtures/controller.radar/pointBorderWidth/scriptable.js index 65bd8480d36..adb76a66518 100644 --- a/test/fixtures/controller.radar/pointBorderWidth/scriptable.js +++ b/test/fixtures/controller.radar/pointBorderWidth/scriptable.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointBorderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 10 - : value > -4 ? 5 - : 2; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: '#ff0000', - borderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 2 - : value > -4 ? 5 - : 10; - }, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointBorderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 10 + : value > -4 ? 5 + : 2; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: '#ff0000', + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 2 + : value > -4 ? 5 + : 10; + }, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointBorderWidth/value.js b/test/fixtures/controller.radar/pointBorderWidth/value.js index 91555695d0a..561ea121b43 100644 --- a/test/fixtures/controller.radar/pointBorderWidth/value.js +++ b/test/fixtures/controller.radar/pointBorderWidth/value.js @@ -1,44 +1,44 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointBorderWidth: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - } - ] - }, - options: { - elements: { - line: { - fill: false - }, - point: { - borderColor: '#00ff00', - borderWidth: 3, - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointBorderWidth: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + elements: { + line: { + fill: false + }, + point: { + borderColor: '#00ff00', + borderWidth: 3, + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointStyle/indexable.js b/test/fixtures/controller.radar/pointStyle/indexable.js index b9cf41dfa11..65afe0be248 100644 --- a/test/fixtures/controller.radar/pointStyle/indexable.js +++ b/test/fixtures/controller.radar/pointStyle/indexable.js @@ -1,60 +1,60 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000', - pointBorderColor: '#ff0000', - pointStyle: [ - 'circle', - 'cross', - 'crossRot', - 'dash', - 'line', - 'rect', - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - borderColor: '#00ff00', - pointStyle: [ - 'line', - 'rect', - 'rectRounded', - 'rectRot', - 'star', - 'triangle' - ], - radius: 10 - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000', + pointBorderColor: '#ff0000', + pointStyle: [ + 'circle', + 'cross', + 'crossRot', + 'dash', + 'line', + 'rect', + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + borderColor: '#00ff00', + pointStyle: [ + 'line', + 'rect', + 'rectRounded', + 'rectRot', + 'star', + 'triangle' + ], + radius: 10 + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointStyle/scriptable.js b/test/fixtures/controller.radar/pointStyle/scriptable.js index eac8628d087..5a793cb1e7c 100644 --- a/test/fixtures/controller.radar/pointStyle/scriptable.js +++ b/test/fixtures/controller.radar/pointStyle/scriptable.js @@ -1,58 +1,58 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#ff0000', - pointBorderColor: '#ff0000', - pointStyle: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? 'rect' - : value > 0 ? 'star' - : value > -8 ? 'cross' - : 'triangle'; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#0000ff', - borderColor: '#0000ff', - pointStyle: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 8 ? 'triangle' - : value > 0 ? 'cross' - : value > -8 ? 'star' - : 'rect'; - }, - radius: 10, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#ff0000', + pointBorderColor: '#ff0000', + pointStyle: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? 'rect' + : value > 0 ? 'star' + : value > -8 ? 'cross' + : 'triangle'; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#0000ff', + borderColor: '#0000ff', + pointStyle: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? 'triangle' + : value > 0 ? 'cross' + : value > -8 ? 'star' + : 'rect'; + }, + radius: 10, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/pointStyle/value.js b/test/fixtures/controller.radar/pointStyle/value.js index 7197528816e..20b0bb48fd0 100644 --- a/test/fixtures/controller.radar/pointStyle/value.js +++ b/test/fixtures/controller.radar/pointStyle/value.js @@ -1,44 +1,44 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#ff0000', - pointStyle: 'star', - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - pointStyle: 'rect', - radius: 10, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#ff0000', + pointStyle: 'star', + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + pointStyle: 'rect', + radius: 10, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/radius/indexable.js b/test/fixtures/controller.radar/radius/indexable.js index 9eb23f09177..da639ea6624 100644 --- a/test/fixtures/controller.radar/radius/indexable.js +++ b/test/fixtures/controller.radar/radius/indexable.js @@ -1,47 +1,47 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#00ff00', - pointRadius: [ - 1, 2, 3, 4, 5, 6 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#ff0000', - radius: [ - 6, 5, 4, 3, 2, 1 - ], - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#00ff00', + pointRadius: [ + 1, 2, 3, 4, 5, 6 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#ff0000', + radius: [ + 6, 5, 4, 3, 2, 1 + ], + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/radius/scriptable.js b/test/fixtures/controller.radar/radius/scriptable.js index d5fc1a8145d..5be5197ea19 100644 --- a/test/fixtures/controller.radar/radius/scriptable.js +++ b/test/fixtures/controller.radar/radius/scriptable.js @@ -1,53 +1,53 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#0000ff', - pointRadius: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 10 - : value > -4 ? 5 - : 2; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#ff0000', - radius: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 2 - : value > -4 ? 5 - : 10; - }, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#0000ff', + pointRadius: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 10 + : value > -4 ? 5 + : 2; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#ff0000', + radius: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 2 + : value > -4 ? 5 + : 10; + }, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/radius/value.js b/test/fixtures/controller.radar/radius/value.js index d7bc6b8e07d..92bb6acd3f9 100644 --- a/test/fixtures/controller.radar/radius/value.js +++ b/test/fixtures/controller.radar/radius/value.js @@ -1,43 +1,43 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBackgroundColor: '#0000ff', - pointRadius: 6 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - backgroundColor: '#00ff00', - radius: 3, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBackgroundColor: '#0000ff', + pointRadius: 6 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + backgroundColor: '#00ff00', + radius: 3, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/rotation/indexable.js b/test/fixtures/controller.radar/rotation/indexable.js index 8f22a396b35..a678634e2e6 100644 --- a/test/fixtures/controller.radar/rotation/indexable.js +++ b/test/fixtures/controller.radar/rotation/indexable.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#00ff00', - pointRotation: [ - 0, 30, 60, 90, 120, 150 - ] - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - borderWidth: 10, - pointStyle: 'line', - rotation: [ - 150, 120, 90, 60, 30, 0 - ], - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#00ff00', + pointRotation: [ + 0, 30, 60, 90, 120, 150 + ] + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + borderWidth: 10, + pointStyle: 'line', + rotation: [ + 150, 120, 90, 60, 30, 0 + ], + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/rotation/scriptable.js b/test/fixtures/controller.radar/rotation/scriptable.js index 890e02c2ab0..1ccb85c56a4 100644 --- a/test/fixtures/controller.radar/rotation/scriptable.js +++ b/test/fixtures/controller.radar/rotation/scriptable.js @@ -1,55 +1,55 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointRotation: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 120 - : value > -4 ? 60 - : 0; - } - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#ff0000', - rotation: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value > 4 ? 0 - : value > -4 ? 60 - : 120; - }, - pointStyle: 'line', - radius: 10, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointRotation: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 120 + : value > -4 ? 60 + : 0; + } + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#ff0000', + rotation: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 4 ? 0 + : value > -4 ? 60 + : 120; + }, + pointStyle: 'line', + radius: 10, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/rotation/value.js b/test/fixtures/controller.radar/rotation/value.js index 5a1959d668d..93306e88a1e 100644 --- a/test/fixtures/controller.radar/rotation/value.js +++ b/test/fixtures/controller.radar/rotation/value.js @@ -1,45 +1,45 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - pointBorderColor: '#0000ff', - pointRotation: 90 - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5], - } - ] - }, - options: { - elements: { - line: { - fill: false, - }, - point: { - borderColor: '#00ff00', - pointStyle: 'line', - radius: 10, - rotation: 0, - } - }, - scales: { - r: { - display: false, - min: -15 - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + pointBorderColor: '#0000ff', + pointRotation: 90 + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5], + } + ] + }, + options: { + elements: { + line: { + fill: false, + }, + point: { + borderColor: '#00ff00', + pointStyle: 'line', + radius: 10, + rotation: 0, + } + }, + scales: { + r: { + display: false, + min: -15 + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.radar/showLine/value.js b/test/fixtures/controller.radar/showLine/value.js index c933450dd06..9182916f116 100644 --- a/test/fixtures/controller.radar/showLine/value.js +++ b/test/fixtures/controller.radar/showLine/value.js @@ -1,54 +1,54 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: [0, 1, 2, 3, 4, 5], - datasets: [ - { - // option in dataset - data: [0, 5, 10, null, -10, -5], - backgroundColor: '#ff0000', - fill: false, - showLine: true - }, - { - // option in element (fallback) - data: [4, -5, -10, null, 10, 5] - }, - { - data: [1, 1, 1, 1, 1, 1], - showLine: true, - backgroundColor: 'rgba(0,0,255,0.5)' - } - ] - }, - options: { - showLine: false, - elements: { - line: { - borderColor: '#ff0000', - backgroundColor: 'rgba(0,255,0,0.5)', - fill: true - } - }, - scales: { - r: { - display: false, - min: -15 - } - }, - plugins: { - legend: false, - title: false, - tooltip: false, - filler: true - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#ff0000', + fill: false, + showLine: true + }, + { + // option in element (fallback) + data: [4, -5, -10, null, 10, 5] + }, + { + data: [1, 1, 1, 1, 1, 1], + showLine: true, + backgroundColor: 'rgba(0,0,255,0.5)' + } + ] + }, + options: { + showLine: false, + elements: { + line: { + borderColor: '#ff0000', + backgroundColor: 'rgba(0,255,0,0.5)', + fill: true + } + }, + scales: { + r: { + display: false, + min: -15 + } + }, + plugins: { + legend: false, + title: false, + tooltip: false, + filler: true + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/controller.scatter/showLine/true.js b/test/fixtures/controller.scatter/showLine/true.js index 7c8732acfa0..34acd44c549 100644 --- a/test/fixtures/controller.scatter/showLine/true.js +++ b/test/fixtures/controller.scatter/showLine/true.js @@ -1,31 +1,31 @@ module.exports = { - description: 'showLine option should draw a line if true', - config: { - type: 'scatter', - data: { - datasets: [{ - data: [{x: 10, y: 15}, {x: 15, y: 10}], - pointRadius: 10, - backgroundColor: 'red', - showLine: true, - label: 'dataset1' - }], - }, - options: { - scales: { - x: { - display: false - }, - y: { - display: false - } - } - } - }, - options: { - canvas: { - width: 256, - height: 256 - } - } + description: 'showLine option should draw a line if true', + config: { + type: 'scatter', + data: { + datasets: [{ + data: [{x: 10, y: 15}, {x: 15, y: 10}], + pointRadius: 10, + backgroundColor: 'red', + showLine: true, + label: 'dataset1' + }], + }, + options: { + scales: { + x: { + display: false + }, + y: { + display: false + } + } + } + }, + options: { + canvas: { + width: 256, + height: 256 + } + } }; diff --git a/test/fixtures/controller.scatter/showLine/undefined.js b/test/fixtures/controller.scatter/showLine/undefined.js index de4cf7fe327..4a969ed6f77 100644 --- a/test/fixtures/controller.scatter/showLine/undefined.js +++ b/test/fixtures/controller.scatter/showLine/undefined.js @@ -1,30 +1,30 @@ module.exports = { - description: 'showLine option should not draw a line if undefined', - config: { - type: 'scatter', - data: { - datasets: [{ - data: [{x: 10, y: 15}, {x: 15, y: 10}], - pointRadius: 10, - backgroundColor: 'red', - label: 'dataset1' - }], - }, - options: { - scales: { - x: { - display: false - }, - y: { - display: false - } - } - } - }, - options: { - canvas: { - width: 256, - height: 256 - } - } + description: 'showLine option should not draw a line if undefined', + config: { + type: 'scatter', + data: { + datasets: [{ + data: [{x: 10, y: 15}, {x: 15, y: 10}], + pointRadius: 10, + backgroundColor: 'red', + label: 'dataset1' + }], + }, + options: { + scales: { + x: { + display: false + }, + y: { + display: false + } + } + } + }, + options: { + canvas: { + width: 256, + height: 256 + } + } }; diff --git a/test/fixtures/core.layouts/long-labels.js b/test/fixtures/core.layouts/long-labels.js index 97afe9abfb4..034207680c6 100644 --- a/test/fixtures/core.layouts/long-labels.js +++ b/test/fixtures/core.layouts/long-labels.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1 is very long one', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6 is very long one'] - }, - options: { - plugins: { - legend: false - }, - scales: { - x: { - type: 'category', - ticks: { - maxRotation: 0, - autoSkip: false - } - }, - y: { - type: 'linear', - position: 'right' - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 150, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1 is very long one', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6 is very long one'] + }, + options: { + plugins: { + legend: false + }, + scales: { + x: { + type: 'category', + ticks: { + maxRotation: 0, + autoSkip: false + } + }, + y: { + type: 'linear', + position: 'right' + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 150, + width: 512 + } + } }; diff --git a/test/fixtures/core.layouts/scriptable.js b/test/fixtures/core.layouts/scriptable.js index f31a6d4a999..a541f94df74 100644 --- a/test/fixtures/core.layouts/scriptable.js +++ b/test/fixtures/core.layouts/scriptable.js @@ -1,49 +1,49 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - layout: { - padding: function(ctx) { - // 10% padding - const horizontalPadding = ctx.chart.width * 0.1; - const verticalPadding = ctx.chart.height * 0.1; - return { - top: verticalPadding, - right: horizontalPadding, - bottom: verticalPadding, - left: horizontalPadding - }; - } - }, - plugins: { - legend: false - }, - scales: { - x: { - type: 'category', - ticks: { - maxRotation: 0, - autoSkip: false - } - }, - y: { - type: 'linear', - position: 'right' - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 150, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + layout: { + padding: function(ctx) { + // 10% padding + const horizontalPadding = ctx.chart.width * 0.1; + const verticalPadding = ctx.chart.height * 0.1; + return { + top: verticalPadding, + right: horizontalPadding, + bottom: verticalPadding, + left: horizontalPadding + }; + } + }, + plugins: { + legend: false + }, + scales: { + x: { + type: 'category', + ticks: { + maxRotation: 0, + autoSkip: false + } + }, + y: { + type: 'linear', + position: 'right' + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 150, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-center.js b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-center.js index e98994242f8..0aa42829a6a 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-center.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-center.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - crossAlign: 'center', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + crossAlign: 'center', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-far.js b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-far.js index d5cfee5b7a3..b0e5dc14a51 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-far.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-far.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - crossAlign: 'far', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + crossAlign: 'far', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-near.js b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-near.js index 659aa2c6a43..744a60a7a59 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-bottom-near.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-bottom-near.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - crossAlign: 'near', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + crossAlign: 'near', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-left-center.js b/test/fixtures/core.scale/crossAlignment/cross-align-left-center.js index a2964b44f77..dd62e7881da 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-left-center.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-left-center.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'left', - ticks: { - crossAlign: 'center', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'left', + ticks: { + crossAlign: 'center', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-left-far-clipped.js b/test/fixtures/core.scale/crossAlignment/cross-align-left-far-clipped.js index 27a936dce2c..435cb64bcc0 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-left-far-clipped.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-left-far-clipped.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'left', - ticks: { - crossAlign: 'far', - }, - afterFit: axis => { - axis.width = 64; - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'left', + ticks: { + crossAlign: 'far', + }, + afterFit: axis => { + axis.width = 64; + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-left-far.js b/test/fixtures/core.scale/crossAlignment/cross-align-left-far.js index a2cdb723bca..82a1ab8f237 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-left-far.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-left-far.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'left', - ticks: { - crossAlign: 'far', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'left', + ticks: { + crossAlign: 'far', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-left-near.js b/test/fixtures/core.scale/crossAlignment/cross-align-left-near.js index 69ea769d4f7..75ca877b255 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-left-near.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-left-near.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'left', - ticks: { - crossAlign: 'near', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'left', + ticks: { + crossAlign: 'near', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-right-center.js b/test/fixtures/core.scale/crossAlignment/cross-align-right-center.js index ab31fd4a42a..94230cbe670 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-right-center.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-right-center.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'right', - ticks: { - crossAlign: 'center', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'right', + ticks: { + crossAlign: 'center', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-right-far-clipped.js b/test/fixtures/core.scale/crossAlignment/cross-align-right-far-clipped.js index 893b293d411..3bd14fff62a 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-right-far-clipped.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-right-far-clipped.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'right', - ticks: { - crossAlign: 'far', - }, - afterFit: axis => { - axis.width = 64; - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - }, - tolerance: 0.1 + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'right', + ticks: { + crossAlign: 'far', + }, + afterFit: axis => { + axis.width = 64; + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + }, + tolerance: 0.1 }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-right-far.js b/test/fixtures/core.scale/crossAlignment/cross-align-right-far.js index dc2d1a16d51..ccde8ff66b1 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-right-far.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-right-far.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'right', - ticks: { - crossAlign: 'far', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'right', + ticks: { + crossAlign: 'far', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-right-near.js b/test/fixtures/core.scale/crossAlignment/cross-align-right-near.js index 7bcba8f86a8..0e94a47d13e 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-right-near.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-right-near.js @@ -1,29 +1,29 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Long long label 1', 'Label2', 'Label3'] - }, - options: { - indexAxis: 'y', - scales: { - y: { - position: 'right', - ticks: { - crossAlign: 'near', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Long long label 1', 'Label2', 'Label3'] + }, + options: { + indexAxis: 'y', + scales: { + y: { + position: 'right', + ticks: { + crossAlign: 'near', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-top-center.js b/test/fixtures/core.scale/crossAlignment/cross-align-top-center.js index 9096a4f37de..0f4ddc20ad8 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-top-center.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-top-center.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - position: 'top', - ticks: { - crossAlign: 'center', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + position: 'top', + ticks: { + crossAlign: 'center', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-top-far.js b/test/fixtures/core.scale/crossAlignment/cross-align-top-far.js index e4d16b97730..4232f0ff42a 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-top-far.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-top-far.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - position: 'top', - ticks: { - crossAlign: 'far', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + position: 'top', + ticks: { + crossAlign: 'far', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/crossAlignment/cross-align-top-near.js b/test/fixtures/core.scale/crossAlignment/cross-align-top-near.js index 707b3cc492b..a8fa9e268bc 100644 --- a/test/fixtures/core.scale/crossAlignment/cross-align-top-near.js +++ b/test/fixtures/core.scale/crossAlignment/cross-align-top-near.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - position: 'top', - ticks: { - crossAlign: 'near', - }, - }, - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + position: 'top', + ticks: { + crossAlign: 'near', + }, + }, + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/label-align-center.js b/test/fixtures/core.scale/label-align-center.js index 1093cecd05a..41adf1df602 100644 --- a/test/fixtures/core.scale/label-align-center.js +++ b/test/fixtures/core.scale/label-align-center.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Label1', 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - align: 'center', - }, - }, - y: { - ticks: { - align: 'center', - } - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'center', + }, + }, + y: { + ticks: { + align: 'center', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/label-align-end.js b/test/fixtures/core.scale/label-align-end.js index b2b5bdf4eb3..36aa984b082 100644 --- a/test/fixtures/core.scale/label-align-end.js +++ b/test/fixtures/core.scale/label-align-end.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Label1', 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - align: 'end', - }, - }, - y: { - ticks: { - align: 'end', - } - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'end', + }, + }, + y: { + ticks: { + align: 'end', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/core.scale/label-align-start.js b/test/fixtures/core.scale/label-align-start.js index f8d96d36e99..fa23a49cfa7 100644 --- a/test/fixtures/core.scale/label-align-start.js +++ b/test/fixtures/core.scale/label-align-start.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3], - }], - labels: ['Label1', 'Label2', 'Label3'] - }, - options: { - scales: { - x: { - ticks: { - align: 'start', - }, - }, - y: { - ticks: { - align: 'start', - } - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'start', + }, + }, + y: { + ticks: { + align: 'start', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/default.js b/test/fixtures/element.line/default.js index 1b55944ecfa..3fe92986d85 100644 --- a/test/fixtures/element.line/default.js +++ b/test/fixtures/element.line/default.js @@ -1,24 +1,24 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/all.js b/test/fixtures/element.line/skip/all.js index 80df30a6b7c..af52ef0f40f 100644 --- a/test/fixtures/element.line/skip/all.js +++ b/test/fixtures/element.line/skip/all.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 0, y: NaN}, {x: NaN, y: 0}, {x: NaN, y: -10}, {x: 19, y: NaN}], - borderColor: 'red', - fill: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 0, y: NaN}, {x: NaN, y: 0}, {x: NaN, y: -10}, {x: 19, y: NaN}], + borderColor: 'red', + fill: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/first-span.js b/test/fixtures/element.line/skip/first-span.js index 7bd53d3eec2..823807de189 100644 --- a/test/fixtures/element.line/skip/first-span.js +++ b/test/fixtures/element.line/skip/first-span.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: true, - spanGaps: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: true, + spanGaps: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/first.js b/test/fixtures/element.line/skip/first.js index 2f3b8a8ccb4..268434154b7 100644 --- a/test/fixtures/element.line/skip/first.js +++ b/test/fixtures/element.line/skip/first.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/last-span.js b/test/fixtures/element.line/skip/last-span.js index 147d8252c66..39a02190247 100644 --- a/test/fixtures/element.line/skip/last-span.js +++ b/test/fixtures/element.line/skip/last-span.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}], - borderColor: 'red', - fill: true, - spanGaps: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}], + borderColor: 'red', + fill: true, + spanGaps: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/last.js b/test/fixtures/element.line/skip/last.js index 6f991a45c4d..239a958a0ba 100644 --- a/test/fixtures/element.line/skip/last.js +++ b/test/fixtures/element.line/skip/last.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}], - borderColor: 'red', - fill: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}], + borderColor: 'red', + fill: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/middle-span.js b/test/fixtures/element.line/skip/middle-span.js index 94bb63716c2..8abfa4c34d8 100644 --- a/test/fixtures/element.line/skip/middle-span.js +++ b/test/fixtures/element.line/skip/middle-span.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: true, - spanGaps: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: true, + spanGaps: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/skip/middle.js b/test/fixtures/element.line/skip/middle.js index 6773e75b50a..af846270fe3 100644 --- a/test/fixtures/element.line/skip/middle.js +++ b/test/fixtures/element.line/skip/middle.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: true, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: true, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/stepped/after.js b/test/fixtures/element.line/stepped/after.js index b9c29556097..7dcd31aeafe 100644 --- a/test/fixtures/element.line/stepped/after.js +++ b/test/fixtures/element.line/stepped/after.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 0, - stepped: 'after' - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 0, + stepped: 'after' + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/stepped/before.js b/test/fixtures/element.line/stepped/before.js index ee4fb6f6ea3..5046066bdd9 100644 --- a/test/fixtures/element.line/stepped/before.js +++ b/test/fixtures/element.line/stepped/before.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 0, - stepped: 'before' - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 0, + stepped: 'before' + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/stepped/default.js b/test/fixtures/element.line/stepped/default.js index 18943ebfc10..6bbe0514147 100644 --- a/test/fixtures/element.line/stepped/default.js +++ b/test/fixtures/element.line/stepped/default.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 0, - stepped: true - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 0, + stepped: true + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/stepped/middle.js b/test/fixtures/element.line/stepped/middle.js index 69ec1d0171d..15092814929 100644 --- a/test/fixtures/element.line/stepped/middle.js +++ b/test/fixtures/element.line/stepped/middle.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 0, - stepped: 'middle' - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 0, + stepped: 'middle' + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/tension/default.js b/test/fixtures/element.line/tension/default.js index d858aa38022..0f4f6203435 100644 --- a/test/fixtures/element.line/tension/default.js +++ b/test/fixtures/element.line/tension/default.js @@ -1,26 +1,26 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/tension/one.js b/test/fixtures/element.line/tension/one.js index 83306599fbf..9994e339ca4 100644 --- a/test/fixtures/element.line/tension/one.js +++ b/test/fixtures/element.line/tension/one.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 1 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 1 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.line/tension/zero.js b/test/fixtures/element.line/tension/zero.js index f13a86c62d7..f251d5ed606 100644 --- a/test/fixtures/element.line/tension/zero.js +++ b/test/fixtures/element.line/tension/zero.js @@ -1,27 +1,27 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - { - data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], - borderColor: 'red', - fill: false, - tension: 0 - } - ] - }, - options: { - scales: { - x: {type: 'linear', display: false, min: 0, max: 20}, - y: {display: false, min: -15, max: 15} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [ + { + data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}], + borderColor: 'red', + fill: false, + tension: 0 + } + ] + }, + options: { + scales: { + x: {type: 'linear', display: false, min: 0, max: 20}, + y: {display: false, min: -15, max: 15} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.point/point-style-image.js b/test/fixtures/element.point/point-style-image.js index 6ac7a2a3dfe..54eddf0da45 100644 --- a/test/fixtures/element.point/point-style-image.js +++ b/test/fixtures/element.point/point-style-image.js @@ -21,36 +21,36 @@ imageContext.closePath(); imageContext.fill(); module.exports = { - config: { - type: 'line', - data: { - labels: [0, 1, 2, 3, 4, 5, 6, 7], - datasets: [{ - data: [0, 0, 0, 0, 0, 0, 0, 0], - showLine: false - }] - }, - options: { - responsive: false, - elements: { - point: { - pointStyle: imageCanvas, - rotation: [0, 45, 90, 135, 180, 225, 270, 315] - } - }, - layout: { - padding: 20 - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5, 6, 7], + datasets: [{ + data: [0, 0, 0, 0, 0, 0, 0, 0], + showLine: false + }] + }, + options: { + responsive: false, + elements: { + point: { + pointStyle: imageCanvas, + rotation: [0, 45, 90, 135, 180, 225, 270, 315] + } + }, + layout: { + padding: 20 + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/element.point/rotation.js b/test/fixtures/element.point/rotation.js index 2d2971d65fb..6d2265e1ee2 100644 --- a/test/fixtures/element.point/rotation.js +++ b/test/fixtures/element.point/rotation.js @@ -1,54 +1,54 @@ 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}; - }) - }; + 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; + return -180 + i * 22.5; }); module.exports = { - config: { - type: 'bubble', - data: { - datasets: datasets - }, - options: { - responsive: 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: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + config: { + type: 'bubble', + data: { + datasets: datasets + }, + options: { + responsive: 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: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/mixed/bar+line.js b/test/fixtures/mixed/bar+line.js index a351b560f0e..f6bbfe8cb3e 100644 --- a/test/fixtures/mixed/bar+line.js +++ b/test/fixtures/mixed/bar+line.js @@ -1,39 +1,39 @@ module.exports = { - config: { - data: { - datasets: [ - { - type: 'line', - data: [6, 16, 3, 19], - borderColor: '#0000ff', - fill: false - }, - { - type: 'bar', - data: [5, 20, 1, 10], - backgroundColor: '#00ff00', - borderColor: '#ff0000' - } - ] - }, - options: { - indexAxis: 'y', - scales: { - horz: { - position: 'top' - }, - vert: { - axis: 'y', - labels: ['a', 'b', 'c', 'd'] - } - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 512 - } - } + config: { + data: { + datasets: [ + { + type: 'line', + data: [6, 16, 3, 19], + borderColor: '#0000ff', + fill: false + }, + { + type: 'bar', + data: [5, 20, 1, 10], + backgroundColor: '#00ff00', + borderColor: '#ff0000' + } + ] + }, + options: { + indexAxis: 'y', + scales: { + horz: { + position: 'top' + }, + vert: { + axis: 'y', + labels: ['a', 'b', 'c', 'd'] + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/plugin.filler/fill-line-dataset-interpolated.js b/test/fixtures/plugin.filler/fill-line-dataset-interpolated.js index c2cbc845ab3..ff94edf538a 100644 --- a/test/fixtures/plugin.filler/fill-line-dataset-interpolated.js +++ b/test/fixtures/plugin.filler/fill-line-dataset-interpolated.js @@ -2,71 +2,71 @@ const data1 = []; const data2 = []; const data3 = []; for (let i = 0; i < 200; i++) { - const a = i / Math.PI / 10; + const a = i / Math.PI / 10; - data1.push({x: i, y: i < 86 || i > 104 && i < 178 ? Math.sin(a) : NaN}); + data1.push({x: i, y: i < 86 || i > 104 && i < 178 ? Math.sin(a) : NaN}); - if (i % 10 === 0) { - data2.push({x: i, y: Math.cos(a)}); - } + if (i % 10 === 0) { + data2.push({x: i, y: Math.cos(a)}); + } - if (i % 15 === 0) { - data3.push({x: i, y: Math.cos(a + Math.PI / 2)}); - } + if (i % 15 === 0) { + data3.push({x: i, y: Math.cos(a + Math.PI / 2)}); + } } module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - borderColor: 'rgba(255, 0, 0, 0.5)', - backgroundColor: 'rgba(255, 0, 0, 0.25)', - data: data1, - fill: false, - }, { - borderColor: 'rgba(0, 0, 255, 0.5)', - backgroundColor: 'rgba(0, 0, 255, 0.25)', - data: data2, - fill: 0, - }, { - borderColor: 'rgba(0, 255, 0, 0.5)', - backgroundColor: 'rgba(0, 255, 0, 0.25)', - data: data3, - fill: 1, - }] - }, - options: { - animation: false, - responsive: false, - datasets: { - line: { - tension: 0.4, - borderWidth: 1, - pointRadius: 1.5, - } - }, - plugins: { - legend: false, - title: false, - tooltip: false - }, - scales: { - x: { - type: 'linear', - display: false - }, - y: { - type: 'linear', - display: false - } - } - } - }, - options: { - canvas: { - height: 512, - width: 512 - } - } + config: { + type: 'line', + data: { + datasets: [{ + borderColor: 'rgba(255, 0, 0, 0.5)', + backgroundColor: 'rgba(255, 0, 0, 0.25)', + data: data1, + fill: false, + }, { + borderColor: 'rgba(0, 0, 255, 0.5)', + backgroundColor: 'rgba(0, 0, 255, 0.25)', + data: data2, + fill: 0, + }, { + borderColor: 'rgba(0, 255, 0, 0.5)', + backgroundColor: 'rgba(0, 255, 0, 0.25)', + data: data3, + fill: 1, + }] + }, + options: { + animation: false, + responsive: false, + datasets: { + line: { + tension: 0.4, + borderWidth: 1, + pointRadius: 1.5, + } + }, + plugins: { + legend: false, + title: false, + tooltip: false + }, + scales: { + x: { + type: 'linear', + display: false + }, + y: { + type: 'linear', + display: false + } + } + } + }, + options: { + canvas: { + height: 512, + width: 512 + } + } }; diff --git a/test/fixtures/plugin.filler/fill-radar-dataset-order.js b/test/fixtures/plugin.filler/fill-radar-dataset-order.js index 1e800486fcc..9747bb37594 100644 --- a/test/fixtures/plugin.filler/fill-radar-dataset-order.js +++ b/test/fixtures/plugin.filler/fill-radar-dataset-order.js @@ -1,33 +1,33 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: ['English', 'Maths', 'Physics', 'Chemistry', 'Biology', 'History'], - datasets: [ - { - order: 1, - borderColor: '#D50000', - backgroundColor: 'rgba(245, 205, 121,0.5)', - data: [65, 75, 70, 80, 60, 80] - }, - { - order: 0, - backgroundColor: 'rgba(0, 168, 255,1)', - data: [54, 65, 60, 70, 70, 75] - } - ] - }, - options: { - plugins: { - legend: false, - title: false, - tooltip: false - }, - scales: { - r: { - display: false - } - } - } - } + config: { + type: 'radar', + data: { + labels: ['English', 'Maths', 'Physics', 'Chemistry', 'Biology', 'History'], + datasets: [ + { + order: 1, + borderColor: '#D50000', + backgroundColor: 'rgba(245, 205, 121,0.5)', + data: [65, 75, 70, 80, 60, 80] + }, + { + order: 0, + backgroundColor: 'rgba(0, 168, 255,1)', + data: [54, 65, 60, 70, 70, 75] + } + ] + }, + options: { + plugins: { + legend: false, + title: false, + tooltip: false + }, + scales: { + r: { + display: false + } + } + } + } }; diff --git a/test/fixtures/plugin.legend/title/bottom-center-center.js b/test/fixtures/plugin.legend/title/bottom-center-center.js index 487e86708ef..200b73ac58a 100644 --- a/test/fixtures/plugin.legend/title/bottom-center-center.js +++ b/test/fixtures/plugin.legend/title/bottom-center-center.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'bottom', - align: 'center', - title: { - display: true, - position: 'center', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'bottom', + align: 'center', + title: { + display: true, + position: 'center', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/bottom-end-end.js b/test/fixtures/plugin.legend/title/bottom-end-end.js index 71c3e968039..f624512b755 100644 --- a/test/fixtures/plugin.legend/title/bottom-end-end.js +++ b/test/fixtures/plugin.legend/title/bottom-end-end.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'bottom', - align: 'end', - title: { - display: true, - position: 'end', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'bottom', + align: 'end', + title: { + display: true, + position: 'end', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/bottom-start-start.js b/test/fixtures/plugin.legend/title/bottom-start-start.js index 18f2d5a82d4..8f993e14041 100644 --- a/test/fixtures/plugin.legend/title/bottom-start-start.js +++ b/test/fixtures/plugin.legend/title/bottom-start-start.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'bottom', - align: 'start', - title: { - display: true, - position: 'start', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'bottom', + align: 'start', + title: { + display: true, + position: 'start', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/left-center-center.js b/test/fixtures/plugin.legend/title/left-center-center.js index f834ab67c74..b269670dee8 100644 --- a/test/fixtures/plugin.legend/title/left-center-center.js +++ b/test/fixtures/plugin.legend/title/left-center-center.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'left', - align: 'center', - title: { - display: true, - position: 'center', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'left', + align: 'center', + title: { + display: true, + position: 'center', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/left-end-end.js b/test/fixtures/plugin.legend/title/left-end-end.js index ecb01b0dc21..c88b95d1cac 100644 --- a/test/fixtures/plugin.legend/title/left-end-end.js +++ b/test/fixtures/plugin.legend/title/left-end-end.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'left', - align: 'end', - title: { - display: true, - position: 'end', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'left', + align: 'end', + title: { + display: true, + position: 'end', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/left-start-start.js b/test/fixtures/plugin.legend/title/left-start-start.js index feb9bef66dd..06decc309a3 100644 --- a/test/fixtures/plugin.legend/title/left-start-start.js +++ b/test/fixtures/plugin.legend/title/left-start-start.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'left', - align: 'start', - title: { - display: true, - position: 'start', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'left', + align: 'start', + title: { + display: true, + position: 'start', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/right-center-center.js b/test/fixtures/plugin.legend/title/right-center-center.js index bc35dccaccf..c9ba4aaf9e2 100644 --- a/test/fixtures/plugin.legend/title/right-center-center.js +++ b/test/fixtures/plugin.legend/title/right-center-center.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'right', - align: 'center', - title: { - display: true, - position: 'center', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'right', + align: 'center', + title: { + display: true, + position: 'center', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/right-end-end.js b/test/fixtures/plugin.legend/title/right-end-end.js index 1dad12c10f2..4add990ea2a 100644 --- a/test/fixtures/plugin.legend/title/right-end-end.js +++ b/test/fixtures/plugin.legend/title/right-end-end.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'right', - align: 'end', - title: { - display: true, - position: 'end', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'right', + align: 'end', + title: { + display: true, + position: 'end', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/right-start-start.js b/test/fixtures/plugin.legend/title/right-start-start.js index fee2f38ef0a..43e9e00430f 100644 --- a/test/fixtures/plugin.legend/title/right-start-start.js +++ b/test/fixtures/plugin.legend/title/right-start-start.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'right', - align: 'start', - title: { - display: true, - position: 'start', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'right', + align: 'start', + title: { + display: true, + position: 'start', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/top-center-center.js b/test/fixtures/plugin.legend/title/top-center-center.js index 4ea2e46f027..1f94799c107 100644 --- a/test/fixtures/plugin.legend/title/top-center-center.js +++ b/test/fixtures/plugin.legend/title/top-center-center.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'top', - align: 'center', - title: { - display: true, - position: 'center', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'top', + align: 'center', + title: { + display: true, + position: 'center', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/top-end-end.js b/test/fixtures/plugin.legend/title/top-end-end.js index faa873985b7..22210df59f9 100644 --- a/test/fixtures/plugin.legend/title/top-end-end.js +++ b/test/fixtures/plugin.legend/title/top-end-end.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'top', - align: 'end', - title: { - display: true, - position: 'end', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'top', + align: 'end', + title: { + display: true, + position: 'end', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.legend/title/top-start-start.js b/test/fixtures/plugin.legend/title/top-start-start.js index 9a893fbaf8b..38430746acb 100644 --- a/test/fixtures/plugin.legend/title/top-start-start.js +++ b/test/fixtures/plugin.legend/title/top-start-start.js @@ -1,36 +1,36 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [ - {label: 'a', data: []}, - {label: 'b', data: []}, - {label: 'c', data: []} - ] - }, - options: { - plugins: { - legend: { - position: 'top', - align: 'start', - title: { - display: true, - position: 'start', - text: 'title' - } - } - }, - scales: { - x: {display: false}, - y: {display: false} - } - } - }, - options: { - spriteText: true, - canvas: { - height: 256, - width: 256 - } - } + config: { + type: 'line', + data: { + datasets: [ + {label: 'a', data: []}, + {label: 'b', data: []}, + {label: 'c', data: []} + ] + }, + options: { + plugins: { + legend: { + position: 'top', + align: 'start', + title: { + display: true, + position: 'start', + text: 'title' + } + } + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 256 + } + } }; diff --git a/test/fixtures/plugin.tooltip/opacity.js b/test/fixtures/plugin.tooltip/opacity.js index 3fef618f006..4726fb832c7 100644 --- a/test/fixtures/plugin.tooltip/opacity.js +++ b/test/fixtures/plugin.tooltip/opacity.js @@ -13,94 +13,94 @@ var pattern = patternContext.createPattern(patternCanvas, 'repeat'); var gradient; module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - data: [8, 8, 8, 8, 8, 8, 7, 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, 1, 0, 0, 0, 0], - showLine: false - }], - labels: ['', '', '', '', '', '', '', '', '', '', ''] - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - }, - elements: { - line: { - fill: false - } - }, - plugins: { - legend: false, - title: false, - filler: 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; + config: { + type: 'line', + data: { + datasets: [{ + data: [8, 8, 8, 8, 8, 8, 7, 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, 1, 0, 0, 0, 0], + showLine: false + }], + labels: ['', '', '', '', '', '', '', '', '', '', ''] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + }, + elements: { + line: { + fill: false + } + }, + plugins: { + legend: false, + title: false, + filler: 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; + 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.x, - clientY: rect.top + point.y - }; - chart._handleEvent(event); - chart.tooltip.handleEvent(event); - chart.tooltip.opacity = j / 10; - chart.tooltip.draw(chart.ctx); - } - } - } - }] - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + 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.x, + clientY: rect.top + point.y + }; + chart._handleEvent(event); + chart.tooltip.handleEvent(event); + chart.tooltip.opacity = j / 10; + chart.tooltip.draw(chart.ctx); + } + } + } + }] + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/plugin.tooltip/point-style.js b/test/fixtures/plugin.tooltip/point-style.js index de60be92ff5..f14dc83b5f0 100644 --- a/test/fixtures/plugin.tooltip/point-style.js +++ b/test/fixtures/plugin.tooltip/point-style.js @@ -1,76 +1,76 @@ const pointStyles = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle']; function newDataset(pointStyle, i) { - return { - label: '', - data: pointStyles.map(() => i), - pointStyle: pointStyle, - pointBackgroundColor: '#0000ff', - pointBorderColor: '#00ff00', - showLine: false - }; + return { + label: '', + data: pointStyles.map(() => i), + pointStyle: pointStyle, + pointBackgroundColor: '#0000ff', + pointBorderColor: '#00ff00', + showLine: false + }; } module.exports = { - config: { - type: 'line', - data: { - datasets: pointStyles.map((pointStyle, i) => newDataset(pointStyle, i)), - labels: pointStyles.map(() => '') - }, - options: { - scales: { - x: {display: false}, - y: {display: false} - }, - elements: { - line: { - fill: false - } - }, - plugins: { - legend: false, - title: false, - filler: false - }, - tooltips: { - mode: 'nearest', - intersect: false, - usePointStyle: true, - callbacks: { - label: function() { - return '\u200b'; - } - } - }, - layout: { - padding: 15 - } - }, - plugins: [{ - afterDraw: function(chart) { - var canvas = chart.canvas; - var rect = canvas.getBoundingClientRect(); - var point, event; + config: { + type: 'line', + data: { + datasets: pointStyles.map((pointStyle, i) => newDataset(pointStyle, i)), + labels: pointStyles.map(() => '') + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + }, + elements: { + line: { + fill: false + } + }, + plugins: { + legend: false, + title: false, + filler: false + }, + tooltips: { + mode: 'nearest', + intersect: false, + usePointStyle: true, + callbacks: { + label: function() { + return '\u200b'; + } + } + }, + layout: { + padding: 15 + } + }, + plugins: [{ + afterDraw: function(chart) { + var canvas = chart.canvas; + var rect = canvas.getBoundingClientRect(); + var point, event; - for (var i = 0; i < pointStyles.length; ++i) { - point = chart.getDatasetMeta(i).data[i]; - event = { - type: 'mousemove', - target: canvas, - clientX: rect.left + point.x, - clientY: rect.top + point.y - }; - chart._handleEvent(event); - chart.tooltip.handleEvent(event); - chart.tooltip.draw(chart.ctx); - } - } - }] - }, - options: { - canvas: { - height: 256, - width: 512 - } - } + for (var i = 0; i < pointStyles.length; ++i) { + point = chart.getDatasetMeta(i).data[i]; + event = { + type: 'mousemove', + target: canvas, + clientX: rect.left + point.x, + clientY: rect.top + point.y + }; + chart._handleEvent(event); + chart.tooltip.handleEvent(event); + chart.tooltip.draw(chart.ctx); + } + } + }] + }, + options: { + canvas: { + height: 256, + width: 512 + } + } }; diff --git a/test/fixtures/scale.category/ticks-from-data.js b/test/fixtures/scale.category/ticks-from-data.js index 61da56d3b34..d002927420d 100644 --- a/test/fixtures/scale.category/ticks-from-data.js +++ b/test/fixtures/scale.category/ticks-from-data.js @@ -1,29 +1,29 @@ module.exports = { - threshold: 0.01, - config: { - type: 'bar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'] - }, - options: { - indexAxis: 'y', - elements: { - bar: { - backgroundColor: '#AAAAAA80', - borderColor: '#80808080', - borderWidth: {bottom: 6, left: 15, top: 6, right: 15} - } - }, - scales: { - x: {display: false}, - y: {display: true} - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'bar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'] + }, + options: { + indexAxis: 'y', + elements: { + bar: { + backgroundColor: '#AAAAAA80', + borderColor: '#80808080', + borderWidth: {bottom: 6, left: 15, top: 6, right: 15} + } + }, + scales: { + x: {display: false}, + y: {display: true} + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.linear/tiny-numbers.js b/test/fixtures/scale.linear/tiny-numbers.js index 63c720e68ad..65ddbbfdfd6 100644 --- a/test/fixtures/scale.linear/tiny-numbers.js +++ b/test/fixtures/scale.linear/tiny-numbers.js @@ -1,18 +1,18 @@ // Should generate max and min that are not equal when data contains values that are very close to each other module.exports = { - config: { - type: 'scatter', - data: { - datasets: [{ - data: [ - {x: 1, y: 1.8548483304974972}, - {x: 2, y: 1.8548483304974974}, - ] - }], - }, - }, - options: { - spriteText: true - } + config: { + type: 'scatter', + data: { + datasets: [{ + data: [ + {x: 1, y: 1.8548483304974972}, + {x: 2, y: 1.8548483304974974}, + ] + }], + }, + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.radialLinear/anglelines-indexable.js b/test/fixtures/scale.radialLinear/anglelines-indexable.js index 2d0b6cb52d6..df62588bdf7 100644 --- a/test/fixtures/scale.radialLinear/anglelines-indexable.js +++ b/test/fixtures/scale.radialLinear/anglelines-indexable.js @@ -1,28 +1,28 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: ['A', 'B', 'C', 'D', 'E'] - }, - options: { - responsive: false, - scales: { - r: { - gridLines: { - display: true, - }, - angleLines: { - color: ['red', 'green'], - lineWidth: [1, 5] - }, - pointLabels: { - display: false - }, - ticks: { - display: false - } - } - } - } - } + config: { + type: 'radar', + data: { + labels: ['A', 'B', 'C', 'D', 'E'] + }, + options: { + responsive: false, + scales: { + r: { + gridLines: { + display: true, + }, + angleLines: { + color: ['red', 'green'], + lineWidth: [1, 5] + }, + pointLabels: { + display: false + }, + ticks: { + display: false + } + } + } + } + } }; diff --git a/test/fixtures/scale.radialLinear/anglelines-scriptable.js b/test/fixtures/scale.radialLinear/anglelines-scriptable.js index 96f117103bc..a3d97d1038b 100644 --- a/test/fixtures/scale.radialLinear/anglelines-scriptable.js +++ b/test/fixtures/scale.radialLinear/anglelines-scriptable.js @@ -1,32 +1,32 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: ['A', 'B', 'C', 'D', 'E'] - }, - options: { - responsive: false, - scales: { - r: { - gridLines: { - display: true, - }, - angleLines: { - color: function(context) { - return context.index % 2 === 0 ? 'red' : 'green'; - }, - lineWidth: function(context) { - return context.index % 2 === 0 ? 1 : 5; - }, - }, - pointLabels: { - display: false - }, - ticks: { - display: false - } - } - } - } - } + config: { + type: 'radar', + data: { + labels: ['A', 'B', 'C', 'D', 'E'] + }, + options: { + responsive: false, + scales: { + r: { + gridLines: { + display: true, + }, + angleLines: { + color: function(context) { + return context.index % 2 === 0 ? 'red' : 'green'; + }, + lineWidth: function(context) { + return context.index % 2 === 0 ? 1 : 5; + }, + }, + pointLabels: { + display: false + }, + ticks: { + display: false + } + } + } + } + } }; diff --git a/test/fixtures/scale.radialLinear/gridlines-scriptable.js b/test/fixtures/scale.radialLinear/gridlines-scriptable.js index 280875c1e6a..fb1dcd527e4 100644 --- a/test/fixtures/scale.radialLinear/gridlines-scriptable.js +++ b/test/fixtures/scale.radialLinear/gridlines-scriptable.js @@ -1,34 +1,34 @@ module.exports = { - config: { - type: 'radar', - data: { - labels: ['A', 'B', 'C', 'D', 'E'] - }, - options: { - responsive: false, - scales: { - r: { - gridLines: { - display: true, - color: function(context) { - return context.index % 2 === 0 ? 'red' : 'green'; - }, - lineWidth: function(context) { - return context.index % 2 === 0 ? 1 : 5; - }, - }, - angleLines: { - color: 'rgba(255, 255, 255, 0.5)', - lineWidth: 2 - }, - pointLabels: { - display: false - }, - ticks: { - display: false - } - } - } - } - } + config: { + type: 'radar', + data: { + labels: ['A', 'B', 'C', 'D', 'E'] + }, + options: { + responsive: false, + scales: { + r: { + gridLines: { + display: true, + color: function(context) { + return context.index % 2 === 0 ? 'red' : 'green'; + }, + lineWidth: function(context) { + return context.index % 2 === 0 ? 1 : 5; + }, + }, + angleLines: { + color: 'rgba(255, 255, 255, 0.5)', + lineWidth: 2 + }, + pointLabels: { + display: false + }, + ticks: { + display: false + } + } + } + } + } }; diff --git a/test/fixtures/scale.time/autoskip-major.js b/test/fixtures/scale.time/autoskip-major.js index f09f27a436e..e207d697c38 100644 --- a/test/fixtures/scale.time/autoskip-major.js +++ b/test/fixtures/scale.time/autoskip-major.js @@ -1,41 +1,41 @@ var date = moment('Jan 01 1990', 'MMM DD YYYY'); var data = []; for (var i = 0; i < 60; i++) { - data.push({x: date.valueOf(), y: i}); - date = date.clone().add(1, 'month'); + data.push({x: date.valueOf(), y: i}); + date = date.clone().add(1, 'month'); } module.exports = { - threshold: 0.05, - config: { - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: data, - fill: false - }], - }, - options: { - scales: { - x: { - type: 'time', - ticks: { - major: { - enabled: true - }, - source: 'data', - autoSkip: true, - maxRotation: 0 - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.05, + config: { + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: data, + fill: false + }], + }, + options: { + scales: { + x: { + type: 'time', + ticks: { + major: { + enabled: true + }, + source: 'data', + autoSkip: true, + maxRotation: 0 + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/bar-large-gap-between-data.js b/test/fixtures/scale.time/bar-large-gap-between-data.js index 1b1654cae5b..5d13b07ded3 100644 --- a/test/fixtures/scale.time/bar-large-gap-between-data.js +++ b/test/fixtures/scale.time/bar-large-gap-between-data.js @@ -1,56 +1,56 @@ var date = moment('May 24 2020', 'MMM DD YYYY'); module.exports = { - threshold: 0.05, - config: { - type: 'bar', - data: { - datasets: [{ - backgroundColor: 'rgba(255, 0, 0, 0.5)', - data: [ - { - x: date.clone().add(-2, 'day'), - y: 20, - }, - { - x: date.clone().add(-1, 'day'), - y: 30, - }, - { - x: date, - y: 40, - }, - { - x: date.clone().add(1, 'day'), - y: 50, - }, - { - x: date.clone().add(7, 'day'), - y: 10, - } - ] - }] - }, - options: { - scales: { - x: { - display: false, - type: 'time', - distribution: 'linear', - ticks: { - source: 'auto' - }, - time: { - unit: 'day' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.05, + config: { + type: 'bar', + data: { + datasets: [{ + backgroundColor: 'rgba(255, 0, 0, 0.5)', + data: [ + { + x: date.clone().add(-2, 'day'), + y: 20, + }, + { + x: date.clone().add(-1, 'day'), + y: 30, + }, + { + x: date, + y: 40, + }, + { + x: date.clone().add(1, 'day'), + y: 50, + }, + { + x: date.clone().add(7, 'day'), + y: 10, + } + ] + }] + }, + options: { + scales: { + x: { + display: false, + type: 'time', + distribution: 'linear', + ticks: { + source: 'auto' + }, + time: { + unit: 'day' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/custom-parser.js b/test/fixtures/scale.time/custom-parser.js index c34193dbd36..e3496e04f62 100644 --- a/test/fixtures/scale.time/custom-parser.js +++ b/test/fixtures/scale.time/custom-parser.js @@ -1,40 +1,40 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['foo', 'bar'], - datasets: [{ - data: [0, 1], - fill: false - }], - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom', - time: { - unit: 'day', - round: true, - parser: function(label) { - return label === 'foo' ? - moment('2000/01/02', 'YYYY/MM/DD') : - moment('2016/05/08', 'YYYY/MM/DD'); - } - }, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 256, height: 128} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['foo', 'bar'], + datasets: [{ + data: [0, 1], + fill: false + }], + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom', + time: { + unit: 'day', + round: true, + parser: function(label) { + return label === 'foo' ? + moment('2000/01/02', 'YYYY/MM/DD') : + moment('2016/05/08', 'YYYY/MM/DD'); + } + }, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 256, height: 128} + } }; diff --git a/test/fixtures/scale.time/data-ty.js b/test/fixtures/scale.time/data-ty.js index 507a4fc80e7..5565c3e0d33 100644 --- a/test/fixtures/scale.time/data-ty.js +++ b/test/fixtures/scale.time/data-ty.js @@ -1,58 +1,58 @@ function newDateFromRef(days) { - return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); + return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); } module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - datasets: [{ - data: [{ - t: newDateFromRef(0), - y: 1 - }, { - t: newDateFromRef(1), - y: 10 - }, { - t: newDateFromRef(2), - y: 0 - }, { - t: newDateFromRef(4), - y: 5 - }, { - t: newDateFromRef(6), - y: 77 - }, { - t: newDateFromRef(7), - y: 9 - }, { - t: newDateFromRef(9), - y: 5 - }], - fill: false, - parsing: { - xAxisKey: 't' - } - }], - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom', - ticks: { - maxRotation: 0 - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 800, height: 200} - } + threshold: 0.01, + config: { + type: 'line', + data: { + datasets: [{ + data: [{ + t: newDateFromRef(0), + y: 1 + }, { + t: newDateFromRef(1), + y: 10 + }, { + t: newDateFromRef(2), + y: 0 + }, { + t: newDateFromRef(4), + y: 5 + }, { + t: newDateFromRef(6), + y: 77 + }, { + t: newDateFromRef(7), + y: 9 + }, { + t: newDateFromRef(9), + y: 5 + }], + fill: false, + parsing: { + xAxisKey: 't' + } + }], + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom', + ticks: { + maxRotation: 0 + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 800, height: 200} + } }; diff --git a/test/fixtures/scale.time/data-xy.js b/test/fixtures/scale.time/data-xy.js index 7230312b5ae..4e582a2977c 100644 --- a/test/fixtures/scale.time/data-xy.js +++ b/test/fixtures/scale.time/data-xy.js @@ -1,55 +1,55 @@ function newDateFromRef(days) { - return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); + return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); } module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - datasets: [{ - data: [{ - x: newDateFromRef(0), - y: 1 - }, { - x: newDateFromRef(1), - y: 10 - }, { - x: newDateFromRef(2), - y: 0 - }, { - x: newDateFromRef(4), - y: 5 - }, { - x: newDateFromRef(6), - y: 77 - }, { - x: newDateFromRef(7), - y: 9 - }, { - x: newDateFromRef(9), - y: 5 - }], - fill: false - }], - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom', - ticks: { - maxRotation: 0 - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 800, height: 200} - } + threshold: 0.01, + config: { + type: 'line', + data: { + datasets: [{ + data: [{ + x: newDateFromRef(0), + y: 1 + }, { + x: newDateFromRef(1), + y: 10 + }, { + x: newDateFromRef(2), + y: 0 + }, { + x: newDateFromRef(4), + y: 5 + }, { + x: newDateFromRef(6), + y: 77 + }, { + x: newDateFromRef(7), + y: 9 + }, { + x: newDateFromRef(9), + y: 5 + }], + fill: false + }], + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom', + ticks: { + maxRotation: 0 + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 800, height: 200} + } }; diff --git a/test/fixtures/scale.time/labels-date.js b/test/fixtures/scale.time/labels-date.js index 9bd82329725..2a79b6fa26b 100644 --- a/test/fixtures/scale.time/labels-date.js +++ b/test/fixtures/scale.time/labels-date.js @@ -1,28 +1,28 @@ function newDateFromRef(days) { - return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); + return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate(); } module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: [newDateFromRef(0), newDateFromRef(1), newDateFromRef(2), newDateFromRef(4), newDateFromRef(6), newDateFromRef(7), newDateFromRef(9)], - fill: false - }, - options: { - scales: { - x: { - type: 'time', - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 1000, height: 200} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: [newDateFromRef(0), newDateFromRef(1), newDateFromRef(2), newDateFromRef(4), newDateFromRef(6), newDateFromRef(7), newDateFromRef(9)], + fill: false + }, + options: { + scales: { + x: { + type: 'time', + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 1000, height: 200} + } }; diff --git a/test/fixtures/scale.time/labels-strings.js b/test/fixtures/scale.time/labels-strings.js index 28b92b239b3..c9e83075c4d 100644 --- a/test/fixtures/scale.time/labels-strings.js +++ b/test/fixtures/scale.time/labels-strings.js @@ -1,23 +1,23 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2015-01-01T12:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'] - }, - options: { - scales: { - x: { - type: 'time', - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 1000, height: 200} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2015-01-01T12:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'] + }, + options: { + scales: { + x: { + type: 'time', + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 1000, height: 200} + } }; diff --git a/test/fixtures/scale.time/labels.js b/test/fixtures/scale.time/labels.js index 071b6ec008e..6f63c191d1a 100644 --- a/test/fixtures/scale.time/labels.js +++ b/test/fixtures/scale.time/labels.js @@ -1,46 +1,46 @@ var timeOpts = { - parser: 'YYYY', - unit: 'year', - displayFormats: { - year: 'YYYY' - } + parser: 'YYYY', + unit: 'year', + displayFormats: { + year: 'YYYY' + } }; module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['1975', '1976', '1977'], - xLabels: ['1985', '1986', '1987'], - yLabels: ['1995', '1996', '1997'] - }, - options: { - scales: { - x: { - type: 'time', - labels: ['2015', '2016', '2017'], - time: timeOpts - }, - x2: { - type: 'time', - position: 'bottom', - time: timeOpts - }, - y: { - type: 'time', - time: timeOpts - }, - y2: { - position: 'left', - type: 'time', - labels: ['2005', '2006', '2007'], - time: timeOpts - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['1975', '1976', '1977'], + xLabels: ['1985', '1986', '1987'], + yLabels: ['1995', '1996', '1997'] + }, + options: { + scales: { + x: { + type: 'time', + labels: ['2015', '2016', '2017'], + time: timeOpts + }, + x2: { + type: 'time', + position: 'bottom', + time: timeOpts + }, + y: { + type: 'time', + time: timeOpts + }, + y2: { + position: 'left', + type: 'time', + labels: ['2005', '2006', '2007'], + time: timeOpts + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/negative-times.js b/test/fixtures/scale.time/negative-times.js index f48ecf1d6e9..9b3e80d13fd 100644 --- a/test/fixtures/scale.time/negative-times.js +++ b/test/fixtures/scale.time/negative-times.js @@ -1,41 +1,41 @@ module.exports = { - config: { - type: 'line', - data: { - datasets: [{ - fill: true, - backgroundColor: 'red', - data: [ - {x: -1000000, y: 1}, - {x: 1000000000, y: 2} - ] - }] - }, - options: { - scales: { - x: { - type: 'time', - time: { - unit: 'day' - }, - ticks: { - display: false - } - }, - y: { - ticks: { - display: false - } - } - }, - plugins: { - legend: false, - title: false, - tooltip: false - } - } - }, - options: { - canvas: {width: 1000, height: 200} - } + config: { + type: 'line', + data: { + datasets: [{ + fill: true, + backgroundColor: 'red', + data: [ + {x: -1000000, y: 1}, + {x: 1000000000, y: 2} + ] + }] + }, + options: { + scales: { + x: { + type: 'time', + time: { + unit: 'day' + }, + ticks: { + display: false + } + }, + y: { + ticks: { + display: false + } + } + }, + plugins: { + legend: false, + title: false, + tooltip: false + } + } + }, + options: { + canvas: {width: 1000, height: 200} + } }; diff --git a/test/fixtures/scale.time/source-auto-linear.js b/test/fixtures/scale.time/source-auto-linear.js index cc6e961eded..16fb09fbe0f 100644 --- a/test/fixtures/scale.time/source-auto-linear.js +++ b/test/fixtures/scale.time/source-auto-linear.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'auto' - }, - distribution: 'linear' - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'auto' + }, + distribution: 'linear' + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/source-data-linear.js b/test/fixtures/scale.time/source-data-linear.js index 3133979ab48..3e4a671a464 100644 --- a/test/fixtures/scale.time/source-data-linear.js +++ b/test/fixtures/scale.time/source-data-linear.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'data' - }, - distribution: 'linear' - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'data' + }, + distribution: 'linear' + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/source-labels-linear-offset-min-max.js b/test/fixtures/scale.time/source-labels-linear-offset-min-max.js index 3420dca5c6b..6bd09ce783b 100644 --- a/test/fixtures/scale.time/source-labels-linear-offset-min-max.js +++ b/test/fixtures/scale.time/source-labels-linear-offset-min-max.js @@ -1,33 +1,33 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - min: '2012', - max: '2051', - offset: true, - time: { - parser: 'YYYY', - }, - ticks: { - source: 'labels' - }, - distribution: 'linear' - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + min: '2012', + max: '2051', + offset: true, + time: { + parser: 'YYYY', + }, + ticks: { + source: 'labels' + }, + distribution: 'linear' + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/source-labels-linear.js b/test/fixtures/scale.time/source-labels-linear.js index 2eb1f145ef5..7a0a8303ae2 100644 --- a/test/fixtures/scale.time/source-labels-linear.js +++ b/test/fixtures/scale.time/source-labels-linear.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'labels' - }, - distribution: 'linear' - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'labels' + }, + distribution: 'linear' + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-capacity.js b/test/fixtures/scale.time/ticks-capacity.js index 67961f39b60..8cc6200dcfa 100644 --- a/test/fixtures/scale.time/ticks-capacity.js +++ b/test/fixtures/scale.time/ticks-capacity.js @@ -1,28 +1,28 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: [ - '2012-01-01', '2013-01-01', '2014-01-01', '2015-01-01', - '2016-01-01', '2017-01-01', '2018-01-01', '2019-01-01' - ] - }, - options: { - scales: { - x: { - type: 'time', - time: { - unit: 'year' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: [ + '2012-01-01', '2013-01-01', '2014-01-01', '2015-01-01', + '2016-01-01', '2017-01-01', '2018-01-01', '2019-01-01' + ] + }, + options: { + scales: { + x: { + type: 'time', + time: { + unit: 'year' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-minunit.js b/test/fixtures/scale.time/ticks-minunit.js index b3892361d87..05b9595f5a1 100644 --- a/test/fixtures/scale.time/ticks-minunit.js +++ b/test/fixtures/scale.time/ticks-minunit.js @@ -1,27 +1,27 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'], - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'ticks', - time: { - minUnit: 'day' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 256, height: 128} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'], + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'ticks', + time: { + minUnit: 'day' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 256, height: 128} + } }; diff --git a/test/fixtures/scale.time/ticks-reverse-linear-min-max.js b/test/fixtures/scale.time/ticks-reverse-linear-min-max.js index 9d716e57c1b..99f2d1c1cd5 100644 --- a/test/fixtures/scale.time/ticks-reverse-linear-min-max.js +++ b/test/fixtures/scale.time/ticks-reverse-linear-min-max.js @@ -1,33 +1,33 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - min: '2012', - max: '2050', - time: { - parser: 'YYYY' - }, - distribution: 'linear', - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + min: '2012', + max: '2050', + time: { + parser: 'YYYY' + }, + distribution: 'linear', + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-reverse-linear.js b/test/fixtures/scale.time/ticks-reverse-linear.js index 54651cb2281..cf2a804eaff 100644 --- a/test/fixtures/scale.time/ticks-reverse-linear.js +++ b/test/fixtures/scale.time/ticks-reverse-linear.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY' - }, - distribution: 'linear', - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY' + }, + distribution: 'linear', + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-reverse-offset.js b/test/fixtures/scale.time/ticks-reverse-offset.js index c8e5d49aabf..7975115c5a4 100644 --- a/test/fixtures/scale.time/ticks-reverse-offset.js +++ b/test/fixtures/scale.time/ticks-reverse-offset.js @@ -1,32 +1,32 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2021'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - reverse: true, - offset: true, - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'labels', - }, - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2021'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + reverse: true, + offset: true, + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'labels', + }, + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-reverse.js b/test/fixtures/scale.time/ticks-reverse.js index 0a28ab06a7e..fd8daab718d 100644 --- a/test/fixtures/scale.time/ticks-reverse.js +++ b/test/fixtures/scale.time/ticks-reverse.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2021'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'time', - reverse: true, - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'labels', - }, - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2021'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'time', + reverse: true, + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'labels', + }, + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.time/ticks-round.js b/test/fixtures/scale.time/ticks-round.js index 86ed3d76344..92a09bd7ce1 100644 --- a/test/fixtures/scale.time/ticks-round.js +++ b/test/fixtures/scale.time/ticks-round.js @@ -1,28 +1,28 @@ module.exports = { - threshold: 0.05, - config: { - type: 'line', - data: { - labels: ['2015-01-01T20:00:00', '2015-02-02T21:00:00', '2015-02-21T01:00:00'] - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'ticks', - time: { - unit: 'week', - round: 'week' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 512, height: 256} - } + threshold: 0.05, + config: { + type: 'line', + data: { + labels: ['2015-01-01T20:00:00', '2015-02-02T21:00:00', '2015-02-21T01:00:00'] + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'ticks', + time: { + unit: 'week', + round: 'week' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 512, height: 256} + } }; diff --git a/test/fixtures/scale.time/ticks-stepsize.js b/test/fixtures/scale.time/ticks-stepsize.js index 2cf92675d64..dd5f2e3a379 100644 --- a/test/fixtures/scale.time/ticks-stepsize.js +++ b/test/fixtures/scale.time/ticks-stepsize.js @@ -1,28 +1,28 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2015-01-01T20:00:00', '2015-01-01T21:00:00'] - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'ticks', - time: { - unit: 'hour', - stepSize: 2 - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 512, height: 128} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2015-01-01T20:00:00', '2015-01-01T21:00:00'] + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'ticks', + time: { + unit: 'hour', + stepSize: 2 + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 512, height: 128} + } }; diff --git a/test/fixtures/scale.time/ticks-unit.js b/test/fixtures/scale.time/ticks-unit.js index 3cd7a8c3228..b725c5ff1bf 100644 --- a/test/fixtures/scale.time/ticks-unit.js +++ b/test/fixtures/scale.time/ticks-unit.js @@ -1,26 +1,26 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'], - }, - options: { - scales: { - x: { - type: 'time', - time: { - unit: 'hour', - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true, - canvas: {width: 1200, height: 200} - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'], + }, + options: { + scales: { + x: { + type: 'time', + time: { + unit: 'hour', + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: {width: 1200, height: 200} + } }; diff --git a/test/fixtures/scale.timeseries/financial-daily.js b/test/fixtures/scale.timeseries/financial-daily.js index 49ef34654b8..66adfcfd834 100644 --- a/test/fixtures/scale.timeseries/financial-daily.js +++ b/test/fixtures/scale.timeseries/financial-daily.js @@ -1,58 +1,58 @@ const data = [{x: 631180800000, y: 31.80}, {x: 631267200000, y: 30.20}, {x: 631353600000, y: 29.84}, {x: 631440000000, y: 29.72}, {x: 631526400000, y: 28.91}, {x: 631785600000, y: 29.55}, {x: 631872000000, y: 30.39}, {x: 631958400000, y: 29.54}, {x: 632044800000, y: 28.86}, {x: 632131200000, y: 30.75}, {x: 632390400000, y: 31.86}, {x: 632476800000, y: 33.59}, {x: 632563200000, y: 31.22}, {x: 632649600000, y: 30.12}, {x: 632736000000, y: 30.68}, {x: 632995200000, y: 31.46}, {x: 633081600000, y: 30.77}, {x: 633168000000, y: 30.27}, {x: 633254400000, y: 29.64}, {x: 633340800000, y: 30.53}, {x: 633600000000, y: 30.79}, {x: 633686400000, y: 30.27}, {x: 633772800000, y: 30.18}, {x: 633859200000, y: 27.72}, {x: 633945600000, y: 27.83}, {x: 634204800000, y: 27.82}, {x: 634291200000, y: 29.10}, {x: 634377600000, y: 28.34}, {x: 634464000000, y: 29.52}, {x: 634550400000, y: 28.69}, {x: 634809600000, y: 28.23}, {x: 634896000000, y: 27.45}, {x: 634982400000, y: 27.40}, {x: 635068800000, y: 28.39}, {x: 635155200000, y: 30.03}, {x: 635414400000, y: 31.19}, {x: 635500800000, y: 32.30}, {x: 635587200000, y: 33.84}, {x: 635673600000, y: 32.34}, {x: 635760000000, y: 31.96}, {x: 636019200000, y: 31.95}, {x: 636105600000, y: 32.84}, {x: 636192000000, y: 30.80}, {x: 636278400000, y: 31.54}, {x: 636364800000, y: 30.81}, {x: 636624000000, y: 32.99}, {x: 636710400000, y: 32.25}, {x: 636796800000, y: 33.87}, {x: 636883200000, y: 35.75}, {x: 636969600000, y: 35.71}, {x: 637228800000, y: 36.60}, {x: 637315200000, y: 35.65}, {x: 637401600000, y: 34.36}, {x: 637488000000, y: 33.61}, {x: 637574400000, y: 34.24}, {x: 637833600000, y: 32.79}, {x: 637920000000, y: 34.41}, {x: 638006400000, y: 34.11}, {x: 638092800000, y: 33.91}, {x: 638179200000, y: 33.33}, {x: 638438400000, y: 32.99}, {x: 638524800000, y: 34.17}, {x: 638611200000, y: 33.50}, {x: 638697600000, y: 35.64}, {x: 638784000000, y: 35.50}, {x: 639039600000, y: 33.11}, {x: 639126000000, y: 34.08}, {x: 639212400000, y: 35.69}, {x: 639298800000, y: 38.24}, {x: 639385200000, y: 40.86}, {x: 639644400000, y: 41.99}, {x: 639730800000, y: 44.45}, {x: 639817200000, y: 45.06}, {x: 639903600000, y: 44.32}, {x: 639990000000, y: 43.70}, {x: 640249200000, y: 44.97}, {x: 640335600000, y: 44.92}, {x: 640422000000, y: 44.11}, {x: 640508400000, y: 44.42}, {x: 640594800000, y: 43.90}, {x: 640854000000, y: 41.91}, {x: 640940400000, y: 41.60}, {x: 641026800000, y: 41.84}, {x: 641113200000, y: 42.55}, {x: 641199600000, y: 40.56}, {x: 641458800000, y: 39.99}, {x: 641545200000, y: 43.51}, {x: 641631600000, y: 43.17}, {x: 641718000000, y: 40.52}, {x: 641804400000, y: 41.06}, {x: 642063600000, y: 40.15}, {x: 642150000000, y: 43.82}, {x: 642236400000, y: 43.19}, {x: 642322800000, y: 40.99}, {x: 642409200000, y: 41.16}, {x: 642668400000, y: 41.02}, {x: 642754800000, y: 40.03}, {x: 642841200000, y: 36.46}, {x: 642927600000, y: 39.11}, {x: 643014000000, y: 41.10}, {x: 643273200000, y: 41.15}, {x: 643359600000, y: 39.01}, {x: 643446000000, y: 39.48}, {x: 643532400000, y: 41.89}, {x: 643618800000, y: 40.74}, {x: 643878000000, y: 38.88}, {x: 643964400000, y: 38.11}, {x: 644050800000, y: 40.39}, {x: 644137200000, y: 38.28}, {x: 644223600000, y: 39.96}, {x: 644482800000, y: 39.37}, {x: 644569200000, y: 39.39}, {x: 644655600000, y: 39.62}, {x: 644742000000, y: 38.99}, {x: 644828400000, y: 40.25}, {x: 645087600000, y: 42.85}, {x: 645174000000, y: 45.91}, {x: 645260400000, y: 46.66}, {x: 645346800000, y: 48.08}, {x: 645433200000, y: 51.00}, {x: 645692400000, y: 50.61}, {x: 645778800000, y: 54.55}, {x: 645865200000, y: 53.59}, {x: 645951600000, y: 53.39}, {x: 646038000000, y: 54.61}, {x: 646297200000, y: 55.02}, {x: 646383600000, y: 57.35}, {x: 646470000000, y: 56.95}, {x: 646556400000, y: 60.08}, {x: 646642800000, y: 59.80}, {x: 646902000000, y: 61.29}, {x: 646988400000, y: 63.45}, {x: 647074800000, y: 62.07}, {x: 647161200000, y: 59.01}, {x: 647247600000, y: 59.76}, {x: 647506800000, y: 60.08}, {x: 647593200000, y: 60.96}, {x: 647679600000, y: 60.56}, {x: 647766000000, y: 58.60}, {x: 647852400000, y: 57.40}, {x: 648111600000, y: 59.86}, {x: 648198000000, y: 58.76}, {x: 648284400000, y: 57.54}, {x: 648370800000, y: 57.78}, {x: 648457200000, y: 54.33}, {x: 648716400000, y: 54.57}, {x: 648802800000, y: 53.69}, {x: 648889200000, y: 57.02}, {x: 648975600000, y: 52.30}, {x: 649062000000, y: 49.79}, {x: 649321200000, y: 47.40}, {x: 649407600000, y: 45.44}, {x: 649494000000, y: 46.75}, {x: 649580400000, y: 44.19}, {x: 649666800000, y: 43.05}, {x: 649926000000, y: 43.99}, {x: 650012400000, y: 45.99}, {x: 650098800000, y: 42.15}, {x: 650185200000, y: 41.84}, {x: 650271600000, y: 43.30}, {x: 650530800000, y: 41.57}, {x: 650617200000, y: 42.13}, {x: 650703600000, y: 43.29}, {x: 650790000000, y: 43.98}, {x: 650876400000, y: 44.51}, {x: 651135600000, y: 45.50}, {x: 651222000000, y: 43.63}, {x: 651308400000, y: 41.93}, {x: 651394800000, y: 38.41}, {x: 651481200000, y: 41.01}, {x: 651740400000, y: 38.17}, {x: 651826800000, y: 38.32}, {x: 651913200000, y: 38.27}, {x: 651999600000, y: 36.10}, {x: 652086000000, y: 34.62}, {x: 652345200000, y: 33.91}, {x: 652431600000, y: 34.25}, {x: 652518000000, y: 33.97}, {x: 652604400000, y: 35.11}, {x: 652690800000, y: 35.05}, {x: 652950000000, y: 36.37}, {x: 653036400000, y: 35.54}, {x: 653122800000, y: 35.80}, {x: 653209200000, y: 36.75}, {x: 653295600000, y: 35.48}, {x: 653554800000, y: 36.78}, {x: 653641200000, y: 34.35}, {x: 653727600000, y: 32.62}, {x: 653814000000, y: 32.66}, {x: 653900400000, y: 31.45}, {x: 654159600000, y: 29.29}, {x: 654246000000, y: 31.18}, {x: 654332400000, y: 29.47}, {x: 654418800000, y: 28.40}, {x: 654505200000, y: 28.21}, {x: 654764400000, y: 27.73}, {x: 654850800000, y: 27.08}, {x: 654937200000, y: 25.32}, {x: 655023600000, y: 25.69}, {x: 655110000000, y: 27.28}, {x: 655369200000, y: 28.53}, {x: 655455600000, y: 27.88}, {x: 655542000000, y: 28.17}, {x: 655628400000, y: 26.22}, {x: 655714800000, y: 26.07}, {x: 655974000000, y: 28.42}, {x: 656060400000, y: 28.27}, {x: 656146800000, y: 29.76}, {x: 656233200000, y: 29.58}, {x: 656319600000, y: 29.41}, {x: 656578800000, y: 29.34}, {x: 656665200000, y: 29.45}, {x: 656751600000, y: 27.93}, {x: 656838000000, y: 27.68}, {x: 656924400000, y: 27.42}, {x: 657187200000, y: 25.79}, {x: 657273600000, y: 25.84}, {x: 657360000000, y: 26.00}, {x: 657446400000, y: 26.57}, {x: 657532800000, y: 26.66}, {x: 657792000000, y: 26.40}, {x: 657878400000, y: 28.06}, {x: 657964800000, y: 27.58}, {x: 658051200000, y: 27.18}, {x: 658137600000, y: 27.71}, {x: 658396800000, y: 26.37}, {x: 658483200000, y: 26.53}, {x: 658569600000, y: 26.19}, {x: 658656000000, y: 25.29}, {x: 658742400000, y: 27.33}, {x: 659001600000, y: 26.08}, {x: 659088000000, y: 26.26}, {x: 659174400000, y: 26.35}, {x: 659260800000, y: 24.88}, {x: 659347200000, y: 23.71}, {x: 659606400000, y: 25.77}, {x: 659692800000, y: 26.03}, {x: 659779200000, y: 27.38}, {x: 659865600000, y: 27.82}, {x: 659952000000, y: 27.61}, {x: 660211200000, y: 26.15}, {x: 660297600000, y: 26.79}, {x: 660384000000, y: 26.78}, {x: 660470400000, y: 28.69}, {x: 660556800000, y: 29.38}, {x: 660816000000, y: 30.16}, {x: 660902400000, y: 29.42}, {x: 660988800000, y: 29.06}, {x: 661075200000, y: 28.05}, {x: 661161600000, y: 29.48}, {x: 661420800000, y: 28.48}, {x: 661507200000, y: 28.67}, {x: 661593600000, y: 28.27}, {x: 661680000000, y: 27.29}, {x: 661766400000, y: 26.88}, {x: 662025600000, y: 27.12}, {x: 662112000000, y: 27.02}, {x: 662198400000, y: 27.08}, {x: 662284800000, y: 24.53}, {x: 662371200000, y: 25.19}, {x: 662630400000, y: 26.70}, {x: 662716800000, y: 27.23}, {x: 662803200000, y: 26.26}, {x: 662889600000, y: 26.46}, {x: 662976000000, y: 25.38}, {x: 663235200000, y: 25.23}, {x: 663321600000, y: 25.53}, {x: 663408000000, y: 25.71}, {x: 663494400000, y: 25.39}, {x: 663580800000, y: 24.35}, {x: 663840000000, y: 23.64}, {x: 663926400000, y: 22.98}, {x: 664012800000, y: 22.75}, {x: 664099200000, y: 22.70}, {x: 664185600000, y: 21.56}, {x: 664444800000, y: 22.65}, {x: 664531200000, y: 21.54}, {x: 664617600000, y: 20.68}, {x: 664704000000, y: 21.37}, {x: 664790400000, y: 22.44}, {x: 665049600000, y: 23.89}, {x: 665136000000, y: 25.02}, {x: 665222400000, y: 26.84}, {x: 665308800000, y: 26.11}, {x: 665395200000, y: 25.91}, {x: 665654400000, y: 27.21}, {x: 665740800000, y: 26.37}, {x: 665827200000, y: 26.81}, {x: 665913600000, y: 26.42}, {x: 666000000000, y: 26.73}, {x: 666259200000, y: 27.25}, {x: 666345600000, y: 25.01}, {x: 666432000000, y: 24.55}, {x: 666518400000, y: 25.34}, {x: 666604800000, y: 25.37}, {x: 666864000000, y: 27.51}, {x: 666950400000, y: 27.51}, {x: 667036800000, y: 28.65}, {x: 667123200000, y: 28.90}, {x: 667209600000, y: 29.22}, {x: 667468800000, y: 29.77}, {x: 667555200000, y: 29.21}, {x: 667641600000, y: 29.81}, {x: 667728000000, y: 27.75}, {x: 667814400000, y: 28.56}, {x: 668073600000, y: 28.06}, {x: 668160000000, y: 26.70}, {x: 668246400000, y: 26.39}, {x: 668332800000, y: 26.42}, {x: 668419200000, y: 29.05}, {x: 668678400000, y: 27.84}, {x: 668764800000, y: 27.67}, {x: 668851200000, y: 26.75}, {x: 668937600000, y: 26.20}, {x: 669024000000, y: 27.33}, {x: 669283200000, y: 27.55}, {x: 669369600000, y: 26.79}, {x: 669456000000, y: 25.29}, {x: 669542400000, y: 25.17}, {x: 669628800000, y: 25.55}, {x: 669888000000, y: 23.87}, {x: 669974400000, y: 22.92}, {x: 670060800000, y: 23.80}, {x: 670147200000, y: 24.18}, {x: 670233600000, y: 22.56}, {x: 670492800000, y: 21.93}, {x: 670579200000, y: 20.96}, {x: 670665600000, y: 21.94}, {x: 670752000000, y: 21.48}, {x: 670838400000, y: 22.17}, {x: 671094000000, y: 22.68}, {x: 671180400000, y: 20.56}, {x: 671266800000, y: 18.98}, {x: 671353200000, y: 19.93}, {x: 671439600000, y: 19.53}, {x: 671698800000, y: 18.93}, {x: 671785200000, y: 19.41}, {x: 671871600000, y: 18.61}, {x: 671958000000, y: 18.88}, {x: 672044400000, y: 18.70}, {x: 672303600000, y: 18.80}, {x: 672390000000, y: 17.72}, {x: 672476400000, y: 17.65}, {x: 672562800000, y: 17.99}, {x: 672649200000, y: 17.01}, {x: 672908400000, y: 17.05}, {x: 672994800000, y: 16.39}, {x: 673081200000, y: 15.96}, {x: 673167600000, y: 15.82}, {x: 673254000000, y: 16.26}, {x: 673513200000, y: 16.33}, {x: 673599600000, y: 15.73}, {x: 673686000000, y: 15.02}, {x: 673772400000, y: 14.51}, {x: 673858800000, y: 14.71}, {x: 674118000000, y: 15.29}, {x: 674204400000, y: 15.46}, {x: 674290800000, y: 15.30}, {x: 674377200000, y: 14.14}, {x: 674463600000, y: 13.94}, {x: 674722800000, y: 13.01}, {x: 674809200000, y: 13.59}, {x: 674895600000, y: 13.67}, {x: 674982000000, y: 13.28}, {x: 675068400000, y: 13.11}, {x: 675327600000, y: 13.52}, {x: 675414000000, y: 14.02}, {x: 675500400000, y: 14.53}, {x: 675586800000, y: 14.61}, {x: 675673200000, y: 14.53}, {x: 675932400000, y: 14.29}, {x: 676018800000, y: 14.46}, {x: 676105200000, y: 14.07}, {x: 676191600000, y: 13.91}, {x: 676278000000, y: 14.08}, {x: 676537200000, y: 13.63}, {x: 676623600000, y: 14.38}, {x: 676710000000, y: 14.86}, {x: 676796400000, y: 14.82}, {x: 676882800000, y: 14.04}, {x: 677142000000, y: 14.63}, {x: 677228400000, y: 14.83}, {x: 677314800000, y: 15.33}, {x: 677401200000, y: 14.67}, {x: 677487600000, y: 14.18}, {x: 677746800000, y: 14.40}, {x: 677833200000, y: 14.45}, {x: 677919600000, y: 14.78}, {x: 678006000000, y: 14.93}, {x: 678092400000, y: 14.09}, {x: 678351600000, y: 13.56}, {x: 678438000000, y: 14.26}, {x: 678524400000, y: 14.36}, {x: 678610800000, y: 14.82}, {x: 678697200000, y: 15.96}, {x: 678956400000, y: 15.83}, {x: 679042800000, y: 15.92}, {x: 679129200000, y: 15.29}, {x: 679215600000, y: 16.29}, {x: 679302000000, y: 15.31}, {x: 679561200000, y: 15.13}, {x: 679647600000, y: 15.59}, {x: 679734000000, y: 14.97}, {x: 679820400000, y: 15.81}, {x: 679906800000, y: 15.59}, {x: 680166000000, y: 14.83}, {x: 680252400000, y: 14.57}, {x: 680338800000, y: 14.24}, {x: 680425200000, y: 14.49}, {x: 680511600000, y: 13.80}, {x: 680770800000, y: 14.17}, {x: 680857200000, y: 14.40}, {x: 680943600000, y: 14.31}, {x: 681030000000, y: 13.89}, {x: 681116400000, y: 13.59}, {x: 681375600000, y: 13.36}, {x: 681462000000, y: 13.33}, {x: 681548400000, y: 13.26}, {x: 681634800000, y: 13.71}, {x: 681721200000, y: 13.67}, {x: 681980400000, y: 12.87}, {x: 682066800000, y: 14.03}, {x: 682153200000, y: 13.95}, {x: 682239600000, y: 13.11}, {x: 682326000000, y: 14.05}, {x: 682585200000, y: 14.47}, {x: 682671600000, y: 14.45}, {x: 682758000000, y: 15.14}, {x: 682844400000, y: 15.65}, {x: 682930800000, y: 15.15}, {x: 683190000000, y: 15.22}, {x: 683276400000, y: 15.38}, {x: 683362800000, y: 16.42}, {x: 683449200000, y: 16.26}, {x: 683535600000, y: 16.51}, {x: 683794800000, y: 15.66}, {x: 683881200000, y: 15.88}, {x: 683967600000, y: 16.36}, {x: 684054000000, y: 15.87}, {x: 684140400000, y: 15.61}, {x: 684399600000, y: 16.63}, {x: 684486000000, y: 15.88}, {x: 684572400000, y: 17.21}, {x: 684658800000, y: 18.46}, {x: 684745200000, y: 18.76}, {x: 685004400000, y: 18.39}, {x: 685090800000, y: 18.14}, {x: 685177200000, y: 17.31}, {x: 685263600000, y: 17.21}, {x: 685350000000, y: 17.17}, {x: 685609200000, y: 17.21}, {x: 685695600000, y: 16.86}, {x: 685782000000, y: 17.17}, {x: 685868400000, y: 16.20}, {x: 685954800000, y: 15.14}, {x: 686214000000, y: 15.05}, {x: 686300400000, y: 16.09}, {x: 686386800000, y: 16.40}, {x: 686473200000, y: 15.83}, {x: 686559600000, y: 16.53}, {x: 686818800000, y: 16.32}, {x: 686905200000, y: 16.47}, {x: 686991600000, y: 16.59}, {x: 687078000000, y: 16.51}, {x: 687164400000, y: 17.41}, {x: 687423600000, y: 18.17}, {x: 687510000000, y: 17.63}, {x: 687596400000, y: 17.62}, {x: 687682800000, y: 17.69}, {x: 687769200000, y: 17.54}, {x: 688028400000, y: 16.56}, {x: 688114800000, y: 16.83}, {x: 688201200000, y: 15.98}, {x: 688287600000, y: 16.52}, {x: 688374000000, y: 17.08}, {x: 688636800000, y: 17.27}, {x: 688723200000, y: 18.18}, {x: 688809600000, y: 18.67}, {x: 688896000000, y: 18.97}, {x: 688982400000, y: 20.31}, {x: 689241600000, y: 21.30}, {x: 689328000000, y: 20.96}, {x: 689414400000, y: 20.01}, {x: 689500800000, y: 21.13}, {x: 689587200000, y: 21.52}, {x: 689846400000, y: 22.08}, {x: 689932800000, y: 21.88}, {x: 690019200000, y: 21.18}, {x: 690105600000, y: 22.79}, {x: 690192000000, y: 22.51}, {x: 690451200000, y: 23.66}, {x: 690537600000, y: 23.43}, {x: 690624000000, y: 24.08}, {x: 690710400000, y: 24.83}, {x: 690796800000, y: 23.49}, {x: 691056000000, y: 23.43}, {x: 691142400000, y: 23.98}, {x: 691228800000, y: 24.52}, {x: 691315200000, y: 23.32}, {x: 691401600000, y: 23.63}, {x: 691660800000, y: 21.74}, {x: 691747200000, y: 20.03}, {x: 691833600000, y: 20.37}, {x: 691920000000, y: 21.09}, {x: 692006400000, y: 21.33}, {x: 692265600000, y: 20.48}, {x: 692352000000, y: 20.15}, {x: 692438400000, y: 20.33}, {x: 692524800000, y: 19.53}, {x: 692611200000, y: 19.34}, {x: 692870400000, y: 18.63}, {x: 692956800000, y: 18.42}, {x: 693043200000, y: 19.49}, {x: 693129600000, y: 18.75}, {x: 693216000000, y: 18.11}, {x: 693475200000, y: 17.40}, {x: 693561600000, y: 17.40}, {x: 693648000000, y: 17.73}, {x: 693734400000, y: 18.36}, {x: 693820800000, y: 18.14}, {x: 694080000000, y: 18.71}, {x: 694166400000, y: 17.97}, {x: 694252800000, y: 18.90}, {x: 694339200000, y: 18.31}, {x: 694425600000, y: 18.67}, {x: 694684800000, y: 18.78}, {x: 694771200000, y: 19.53}, {x: 694857600000, y: 19.41}, {x: 694944000000, y: 19.42}, {x: 695030400000, y: 20.29}, {x: 695289600000, y: 21.08}, {x: 695376000000, y: 20.69}, {x: 695462400000, y: 21.37}, {x: 695548800000, y: 20.67}, {x: 695635200000, y: 20.79}, {x: 695894400000, y: 20.39}, {x: 695980800000, y: 19.98}, {x: 696067200000, y: 19.35}, {x: 696153600000, y: 18.60}, {x: 696240000000, y: 18.67}, {x: 696499200000, y: 19.41}, {x: 696585600000, y: 20.62}, {x: 696672000000, y: 21.09}, {x: 696758400000, y: 21.43}, {x: 696844800000, y: 20.31}, {x: 697104000000, y: 19.40}, {x: 697190400000, y: 19.82}, {x: 697276800000, y: 19.55}, {x: 697363200000, y: 19.77}, {x: 697449600000, y: 19.33}, {x: 697708800000, y: 18.75}, {x: 697795200000, y: 18.50}, {x: 697881600000, y: 18.39}, {x: 697968000000, y: 19.19}, {x: 698054400000, y: 19.93}, {x: 698313600000, y: 20.15}, {x: 698400000000, y: 22.09}, {x: 698486400000, y: 20.29}, {x: 698572800000, y: 20.37}, {x: 698659200000, y: 19.06}, {x: 698918400000, y: 20.51}, {x: 699004800000, y: 20.06}, {x: 699091200000, y: 19.54}, {x: 699177600000, y: 17.89}, {x: 699264000000, y: 17.57}, {x: 699523200000, y: 16.88}, {x: 699609600000, y: 17.26}, {x: 699696000000, y: 17.15}, {x: 699782400000, y: 15.73}, {x: 699868800000, y: 15.08}, {x: 700128000000, y: 14.73}, {x: 700214400000, y: 14.58}, {x: 700300800000, y: 14.33}, {x: 700387200000, y: 14.76}, {x: 700473600000, y: 15.44}, {x: 700732800000, y: 16.63}, {x: 700819200000, y: 15.63}, {x: 700905600000, y: 15.61}, {x: 700992000000, y: 16.88}, {x: 701078400000, y: 16.26}, {x: 701337600000, y: 15.95}, {x: 701424000000, y: 15.41}, {x: 701510400000, y: 16.14}, {x: 701596800000, y: 15.77}, {x: 701683200000, y: 15.84}, {x: 701942400000, y: 14.41}, {x: 702028800000, y: 15.62}, {x: 702115200000, y: 15.62}, {x: 702201600000, y: 15.85}, {x: 702288000000, y: 17.18}, {x: 702543600000, y: 17.58}, {x: 702630000000, y: 19.25}, {x: 702716400000, y: 19.77}, {x: 702802800000, y: 20.66}, {x: 702889200000, y: 19.70}, {x: 703148400000, y: 20.01}, {x: 703234800000, y: 19.93}, {x: 703321200000, y: 19.94}, {x: 703407600000, y: 19.77}, {x: 703494000000, y: 19.83}]; module.exports = { - threshold: 0.01, - config: { - data: { - datasets: [{ - data, - type: 'line', - pointRadius: 0, - fill: false, - tension: 0, - borderWidth: 2 - }] - }, - options: { - animation: { - duration: 0 - }, - scales: { - x: { - type: 'timeseries', - offset: true, - ticks: { - major: { - enabled: true, - }, - font: function(context) { - return context.tick && context.tick.major ? {style: 'bold'} : undefined; - }, - source: 'data', - autoSkip: true, - autoSkipPadding: 75, - maxRotation: 0, - sampleSize: 100 - }, - // manually set major ticks so that test passes in all time zones with moment adapter - afterBuildTicks: function(scale) { - const major = [0, 12, 24]; - const ticks = scale.ticks; - for (let i = 0; i < ticks.length; i++) { - ticks[i].major = major.indexOf(i) >= 0; - } - } - }, - y: { - type: 'linear', - gridLines: { - drawBorder: false - } - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + data: { + datasets: [{ + data, + type: 'line', + pointRadius: 0, + fill: false, + tension: 0, + borderWidth: 2 + }] + }, + options: { + animation: { + duration: 0 + }, + scales: { + x: { + type: 'timeseries', + offset: true, + ticks: { + major: { + enabled: true, + }, + font: function(context) { + return context.tick && context.tick.major ? {style: 'bold'} : undefined; + }, + source: 'data', + autoSkip: true, + autoSkipPadding: 75, + maxRotation: 0, + sampleSize: 100 + }, + // manually set major ticks so that test passes in all time zones with moment adapter + afterBuildTicks: function(scale) { + const major = [0, 12, 24]; + const ticks = scale.ticks; + for (let i = 0; i < ticks.length; i++) { + ticks[i].major = major.indexOf(i) >= 0; + } + } + }, + y: { + type: 'linear', + gridLines: { + drawBorder: false + } + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/source-auto.js b/test/fixtures/scale.timeseries/source-auto.js index ee67a5b64d9..e53922bb4fb 100644 --- a/test/fixtures/scale.timeseries/source-auto.js +++ b/test/fixtures/scale.timeseries/source-auto.js @@ -1,30 +1,30 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'auto' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'auto' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/source-data-offset-min-max.js b/test/fixtures/scale.timeseries/source-data-offset-min-max.js index d82a8dd0b4c..e63b5dc5e76 100644 --- a/test/fixtures/scale.timeseries/source-data-offset-min-max.js +++ b/test/fixtures/scale.timeseries/source-data-offset-min-max.js @@ -1,32 +1,32 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - min: '2012', - max: '2051', - offset: true, - time: { - parser: 'YYYY', - }, - ticks: { - source: 'data' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + min: '2012', + max: '2051', + offset: true, + time: { + parser: 'YYYY', + }, + ticks: { + source: 'data' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/source-data.js b/test/fixtures/scale.timeseries/source-data.js index b835cd90bee..2601cb35130 100644 --- a/test/fixtures/scale.timeseries/source-data.js +++ b/test/fixtures/scale.timeseries/source-data.js @@ -1,30 +1,30 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'data' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'data' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/source-labels-offset-min-max.js b/test/fixtures/scale.timeseries/source-labels-offset-min-max.js index 58dabaa1a56..9c71c655711 100644 --- a/test/fixtures/scale.timeseries/source-labels-offset-min-max.js +++ b/test/fixtures/scale.timeseries/source-labels-offset-min-max.js @@ -1,32 +1,32 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - min: '2012', - max: '2051', - offset: true, - time: { - parser: 'YYYY', - }, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + min: '2012', + max: '2051', + offset: true, + time: { + parser: 'YYYY', + }, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/source-labels.js b/test/fixtures/scale.timeseries/source-labels.js index 8b2de21fff1..0087f4a5683 100644 --- a/test/fixtures/scale.timeseries/source-labels.js +++ b/test/fixtures/scale.timeseries/source-labels.js @@ -1,30 +1,30 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2025'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2025'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/ticks-reverse-max.js b/test/fixtures/scale.timeseries/ticks-reverse-max.js index 41d10fc31da..727770969e4 100644 --- a/test/fixtures/scale.timeseries/ticks-reverse-max.js +++ b/test/fixtures/scale.timeseries/ticks-reverse-max.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - max: '2050', - time: { - parser: 'YYYY' - }, - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + max: '2050', + time: { + parser: 'YYYY' + }, + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/ticks-reverse-min-max.js b/test/fixtures/scale.timeseries/ticks-reverse-min-max.js index 9a9a74e2cbe..4d892b65008 100644 --- a/test/fixtures/scale.timeseries/ticks-reverse-min-max.js +++ b/test/fixtures/scale.timeseries/ticks-reverse-min-max.js @@ -1,32 +1,32 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - min: '2012', - max: '2050', - time: { - parser: 'YYYY' - }, - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + min: '2012', + max: '2050', + time: { + parser: 'YYYY' + }, + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/ticks-reverse-min.js b/test/fixtures/scale.timeseries/ticks-reverse-min.js index c1a7c72f199..39f7fe05f82 100644 --- a/test/fixtures/scale.timeseries/ticks-reverse-min.js +++ b/test/fixtures/scale.timeseries/ticks-reverse-min.js @@ -1,31 +1,31 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - min: '2012', - time: { - parser: 'YYYY' - }, - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + min: '2012', + time: { + parser: 'YYYY' + }, + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/fixtures/scale.timeseries/ticks-reverse.js b/test/fixtures/scale.timeseries/ticks-reverse.js index 46e75e1ddcd..237b473d7b6 100644 --- a/test/fixtures/scale.timeseries/ticks-reverse.js +++ b/test/fixtures/scale.timeseries/ticks-reverse.js @@ -1,30 +1,30 @@ module.exports = { - threshold: 0.01, - config: { - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4], fill: false}] - }, - options: { - scales: { - x: { - type: 'timeseries', - time: { - parser: 'YYYY' - }, - reverse: true, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }, - options: { - spriteText: true - } + threshold: 0.01, + config: { + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4], fill: false}] + }, + options: { + scales: { + x: { + type: 'timeseries', + time: { + parser: 'YYYY' + }, + reverse: true, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true + } }; diff --git a/test/index.js b/test/index.js index 0a764c3db42..489fca0a797 100644 --- a/test/index.js +++ b/test/index.js @@ -13,15 +13,15 @@ window.createMockContext = createMockContext; injectWrapperCSS(); jasmine.fixture = { - specs: specsFromFixtures + specs: specsFromFixtures }; jasmine.triggerMouseEvent = triggerMouseEvent; beforeEach(function() { - addMatchers(); + addMatchers(); }); afterEach(function() { - releaseCharts(); + releaseCharts(); }); diff --git a/test/specs/controller.bar.tests.js b/test/specs/controller.bar.tests.js index f73944814c1..b991c6cc445 100644 --- a/test/specs/controller.bar.tests.js +++ b/test/specs/controller.bar.tests.js @@ -1,1609 +1,1609 @@ 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'); - }); - - it('should be constructed', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.type).toEqual('bar'); - expect(meta.data).toEqual([]); - expect(meta.hidden).toBe(null); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(1); - expect(meta.xAxisID).not.toBe(null); - expect(meta.yAxisID).not.toBe(null); - - meta.controller.updateIndex(0); - expect(meta.controller.index).toBe(0); - }); - - it('should use the first scale IDs if the dataset does not specify them', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []} - ], - labels: [] - }, - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.xAxisID).toBe('x'); - expect(meta.yAxisID).toBe('y'); - }); - - it('should correctly count the number of stacks ignoring datasets of other types and hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], type: 'line'}, - {data: [], hidden: true}, - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackCount()).toBe(2); - }); - - it('should correctly count the number of stacks when a group is not specified', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackCount()).toBe(4); - }); - - it('should correctly count the number of stacks when a group is not specified and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackCount()).toBe(1); - }); - - it('should correctly count the number of stacks when a group is not specified and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackCount()).toBe(4); - }); - - it('should correctly count the number of stacks when a group is specified for some', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(3); - }); - - it('should correctly count the number of stacks when a group is specified for some and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(2); - }); - - it('should correctly count the number of stacks when a group is specified for some and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(4); - }); - - it('should correctly count the number of stacks when a group is specified for all', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(2); - }); - - it('should correctly count the number of stacks when a group is specified for all and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(2); - }); - - it('should correctly count the number of stacks when a group is specified for all and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(3); - expect(meta.controller._getStackCount()).toBe(4); - }); - - it('should correctly get the stack index accounting for datasets of other types and hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: [], hidden: true}, - {data: [], type: 'line'}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(3)).toBe(1); - }); - - it('should correctly get the stack index when a group is not specified', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(1); - expect(meta.controller._getStackIndex(2)).toBe(2); - expect(meta.controller._getStackIndex(3)).toBe(3); - }); - - it('should correctly get the stack index when a group is not specified and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(0); - expect(meta.controller._getStackIndex(2)).toBe(0); - expect(meta.controller._getStackIndex(3)).toBe(0); - }); - - it('should correctly get the stack index when a group is not specified and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: []}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(1); - expect(meta.controller._getStackIndex(2)).toBe(2); - expect(meta.controller._getStackIndex(3)).toBe(3); - }); - - it('should correctly get the stack index when a group is specified for some', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(0); - expect(meta.controller._getStackIndex(2)).toBe(1); - expect(meta.controller._getStackIndex(3)).toBe(2); - }); - - it('should correctly get the stack index when a group is specified for some and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(0); - expect(meta.controller._getStackIndex(2)).toBe(1); - expect(meta.controller._getStackIndex(3)).toBe(1); - }); - - it('should correctly get the stack index when a group is specified for some and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: []}, - {data: []} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(1); - expect(meta.controller._getStackIndex(2)).toBe(2); - expect(meta.controller._getStackIndex(3)).toBe(3); - }); - - it('should correctly get the stack index when a group is specified for all', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(0); - expect(meta.controller._getStackIndex(2)).toBe(1); - expect(meta.controller._getStackIndex(3)).toBe(1); - }); - - it('should correctly get the stack index when a group is specified for all and the scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(0); - expect(meta.controller._getStackIndex(2)).toBe(1); - expect(meta.controller._getStackIndex(3)).toBe(1); - }); - - it('should correctly get the stack index when a group is specified for all and the scale is not stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack1'}, - {data: [], stack: 'stack2'}, - {data: [], stack: 'stack2'} - ], - labels: [] - }, - options: { - scales: { - x: { - stacked: false - }, - y: { - stacked: false - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.controller._getStackIndex(0)).toBe(0); - expect(meta.controller._getStackIndex(1)).toBe(1); - expect(meta.controller._getStackIndex(2)).toBe(2); - expect(meta.controller._getStackIndex(3)).toBe(3); - }); - - it('should create bar elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: [10, 15, 0, -4]} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.data.length).toBe(4); // 4 bars created - expect(meta.data[0] instanceof Chart.elements.BarElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.BarElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.BarElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.BarElement).toBe(true); - }); - - it('should update elements when modifying data', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2], - label: 'dataset1' - }, { - data: [10, 15, 0, -4], - label: 'dataset2', - borderColor: 'blue' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - elements: { - bar: { - backgroundColor: 'red', - borderSkipped: 'top', - borderColor: 'green', - borderWidth: 2, - } - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - beginAtZero: false - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.data.length).toBe(4); - - chart.data.datasets[1].data = [1, 2]; // remove 2 items - chart.data.datasets[1].borderWidth = 1; - chart.update(); - - expect(meta.data.length).toBe(2); - expect(meta._parsed.length).toBe(2); - - [ - {x: 89, y: 512}, - {x: 217, y: 0} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].base).toBeCloseToPixel(1024); - expect(meta.data[i].width).toBeCloseToPixel(46); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'red', - borderSkipped: 'top', - borderColor: 'blue', - borderWidth: 1 - })); - }); - - chart.data.datasets[1].data = [1, 2, 3]; // add 1 items - chart.update(); - - expect(meta.data.length).toBe(3); // should add a new meta data item - }); - - it('should get the correct bar points when datasets of different types exist', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2], - label: 'dataset1' - }, { - type: 'line', - data: [4, 6], - label: 'dataset2' - }, { - data: [8, 10], - label: 'dataset3' - }], - labels: ['label1', 'label2'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - beginAtZero: false - } - } - } - }); - - var meta = chart.getDatasetMeta(2); - expect(meta.data.length).toBe(2); - - var bar1 = meta.data[0]; - var bar2 = meta.data[1]; - - expect(bar1.x).toBeCloseToPixel(179); - expect(bar1.y).toBeCloseToPixel(114); - expect(bar2.x).toBeCloseToPixel(435); - expect(bar2.y).toBeCloseToPixel(0); - }); - - it('should get the bar points for hidden dataset', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2], - label: 'dataset1', - hidden: true - }], - labels: ['label1', 'label2'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - min: 0, - max: 2, - display: false - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(2); - - var bar1 = meta.data[0]; - var bar2 = meta.data[1]; - - expect(bar1.x).toBeCloseToPixel(128); - expect(bar1.y).toBeCloseToPixel(256); - expect(bar2.x).toBeCloseToPixel(384); - expect(bar2.y).toBeCloseToPixel(0); - }); - - - it('should update elements when the scales are stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [10, -10, 10, -10], - label: 'dataset1' - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {b: 293, w: 92 / 2, x: 38, y: 146}, - {b: 293, w: 92 / 2, x: 166, y: 439}, - {b: 293, w: 92 / 2, x: 295, y: 146}, - {b: 293, w: 92 / 2, x: 422, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].base).toBeCloseToPixel(values.b); - expect(meta0.data[i].width).toBeCloseToPixel(values.w); - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {b: 146, w: 92 / 2, x: 89, y: 0}, - {b: 293, w: 92 / 2, x: 217, y: 73}, - {b: 146, w: 92 / 2, x: 345, y: 146}, - {b: 439, w: 92 / 2, x: 473, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].base).toBeCloseToPixel(values.b); - expect(meta1.data[i].width).toBeCloseToPixel(values.w); - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should update elements when the scales are stacked and the y axis has a user defined minimum', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [50, 20, 10, 100], - label: 'dataset1' - }, { - data: [50, 80, 90, 0], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true, - min: 50, - max: 100 - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {b: 1024, w: 92 / 2, x: 38, y: 512}, - {b: 1024, w: 92 / 2, x: 166, y: 819}, - {b: 1024, w: 92 / 2, x: 294, y: 922}, - {b: 1024, w: 92 / 2, x: 422.5, y: 0} - ].forEach(function(values, i) { - expect(meta0.data[i].base).toBeCloseToPixel(values.b); - expect(meta0.data[i].width).toBeCloseToPixel(values.w); - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {b: 512, w: 92 / 2, x: 89, y: 0}, - {b: 819.2, w: 92 / 2, x: 217, y: 0}, - {b: 921.6, w: 92 / 2, x: 345, y: 0}, - {b: 0, w: 92 / 2, x: 473.5, y: 0} - ].forEach(function(values, i) { - expect(meta1.data[i].base).toBeCloseToPixel(values.b); - expect(meta1.data[i].width).toBeCloseToPixel(values.w); - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should update elements when only the category scale is stacked', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [20, -10, 10, -10], - label: 'dataset1' - }, { - data: [10, 15, 0, -14], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false, - stacked: true - }, - y: { - type: 'linear', - display: false - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {b: 293, w: 92, x: 64, y: 0}, - {b: 293, w: 92, x: 192, y: 439}, - {b: 293, w: 92, x: 320, y: 146}, - {b: 293, w: 92, x: 448, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].base).toBeCloseToPixel(values.b); - expect(meta0.data[i].width).toBeCloseToPixel(values.w); - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {b: 293, w: 92, x: 64, y: 146}, - {b: 293, w: 92, x: 192, y: 73}, - {b: 293, w: 92, x: 320, y: 293}, - {b: 293, w: 92, x: 448, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].base).toBeCloseToPixel(values.b); - expect(meta1.data[i].width).toBeCloseToPixel(values.w); - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should update elements when the scales are stacked and data is strings', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: ['10', '-10', '10', '-10'], - label: 'dataset1' - }, { - data: ['10', '15', '0', '-4'], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {b: 293, w: 92 / 2, x: 38, y: 146}, - {b: 293, w: 92 / 2, x: 166, y: 439}, - {b: 293, w: 92 / 2, x: 295, y: 146}, - {b: 293, w: 92 / 2, x: 422, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].base).toBeCloseToPixel(values.b); - expect(meta0.data[i].width).toBeCloseToPixel(values.w); - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {b: 146, w: 92 / 2, x: 89, y: 0}, - {b: 293, w: 92 / 2, x: 217, y: 73}, - {b: 146, w: 92 / 2, x: 345, y: 146}, - {b: 439, w: 92 / 2, x: 473, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].base).toBeCloseToPixel(values.b); - expect(meta1.data[i].width).toBeCloseToPixel(values.w); - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should get the correct bar points for grouped stacked chart if the group name is same', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [10, -10, 10, -10], - label: 'dataset1', - stack: 'stack1' - }, { - data: [10, 15, 0, -4], - label: 'dataset2', - stack: 'stack1' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {b: 293, w: 92, x: 64, y: 146}, - {b: 293, w: 92, x: 192, y: 439}, - {b: 293, w: 92, x: 320, y: 146}, - {b: 293, w: 92, x: 448, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].base).toBeCloseToPixel(values.b); - expect(meta0.data[i].width).toBeCloseToPixel(values.w); - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta = chart.getDatasetMeta(1); - - [ - {b: 146, w: 92, x: 64, y: 0}, - {b: 293, w: 92, x: 192, y: 73}, - {b: 146, w: 92, x: 320, y: 146}, - {b: 439, w: 92, x: 448, y: 497} - ].forEach(function(values, i) { - expect(meta.data[i].base).toBeCloseToPixel(values.b); - expect(meta.data[i].width).toBeCloseToPixel(values.w); - expect(meta.data[i].x).toBeCloseToPixel(values.x); - expect(meta.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should get the correct bar points for grouped stacked chart if the group name is different', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2], - stack: 'stack1' - }, { - data: [1, 2], - stack: 'stack2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true, - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - - [ - {x: 89, y: 256}, - {x: 217, y: 0} - ].forEach(function(values, i) { - expect(meta.data[i].base).toBeCloseToPixel(512); - expect(meta.data[i].width).toBeCloseToPixel(46); - expect(meta.data[i].x).toBeCloseToPixel(values.x); - expect(meta.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should get the correct bar points for grouped stacked chart', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2], - stack: 'stack1' - }, { - data: [0.5, 1], - stack: 'stack2' - }, { - data: [0.5, 1], - stack: 'stack2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - stacked: true - } - } - } - }); - - var meta = chart.getDatasetMeta(2); - - [ - {b: 384, x: 89, y: 256}, - {b: 256, x: 217, y: 0} - ].forEach(function(values, i) { - expect(meta.data[i].base).toBeCloseToPixel(values.b); - expect(meta.data[i].width).toBeCloseToPixel(46); - expect(meta.data[i].x).toBeCloseToPixel(values.x); - expect(meta.data[i].y).toBeCloseToPixel(values.y); - }); - }); - - it('should draw all bars', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [], - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(1); - - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it('should set hover styles on bars', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [], - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - elements: { - bar: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2, - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - var bar = meta.data[0]; - - meta.controller.setHoverStyle(bar, 1, 0); - expect(bar.options.backgroundColor).toBe('#E60000'); - expect(bar.options.borderColor).toBe('#0000E6'); - expect(bar.options.borderWidth).toBe(2); - - // Set a dataset style - chart.data.datasets[1].hoverBackgroundColor = 'rgb(128, 128, 128)'; - chart.data.datasets[1].hoverBorderColor = 'rgb(0, 0, 0)'; - chart.data.datasets[1].hoverBorderWidth = 5; - chart.update(); - - meta.controller.setHoverStyle(bar, 1, 0); - expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); - expect(bar.options.borderColor).toBe('rgb(0, 0, 0)'); - expect(bar.options.borderWidth).toBe(5); - - // Should work with array styles so that we can set per bar - chart.data.datasets[1].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)']; - chart.data.datasets[1].hoverBorderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)']; - chart.data.datasets[1].hoverBorderWidth = [2.5, 5]; - chart.update(); - - meta.controller.setHoverStyle(bar, 1, 0); - expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); - expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); - expect(bar.options.borderWidth).toBe(2.5); - }); - - it('should remove a hover style from a bar', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [], - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - elements: { - bar: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2, - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - var bar = meta.data[0]; - var helpers = window.Chart.helpers; - - // Change default - chart.options.elements.bar.backgroundColor = 'rgb(128, 128, 128)'; - chart.options.elements.bar.borderColor = 'rgb(15, 15, 15)'; - chart.options.elements.bar.borderWidth = 3.14; - - chart.update(); - expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); - expect(bar.options.borderColor).toBe('rgb(15, 15, 15)'); - expect(bar.options.borderWidth).toBe(3.14); - meta.controller.setHoverStyle(bar, 1, 0); - expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(128, 128, 128)')); - expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(15, 15, 15)')); - expect(bar.options.borderWidth).toBe(3.14); - meta.controller.removeHoverStyle(bar); - expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); - expect(bar.options.borderColor).toBe('rgb(15, 15, 15)'); - expect(bar.options.borderWidth).toBe(3.14); - - // Should work with array styles so that we can set per bar - chart.data.datasets[1].backgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)']; - chart.data.datasets[1].borderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)']; - chart.data.datasets[1].borderWidth = [2.5, 5]; - - chart.update(); - expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); - expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); - expect(bar.options.borderWidth).toBe(2.5); - meta.controller.setHoverStyle(bar, 1, 0); - expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(255, 255, 255)')); - expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(9, 9, 9)')); - expect(bar.options.borderWidth).toBe(2.5); - meta.controller.removeHoverStyle(bar); - expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); - expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); - expect(bar.options.borderWidth).toBe(2.5); - }); - - describe('Bar width', function() { - beforeEach(function() { - // 2 datasets - this.data = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [{ - data: [10, 20, 30, 40, 50, 60, 70], - }, { - data: [10, 20, 30, 40, 50, 60, 70], - }] - }; - }); - - afterEach(function() { - var chart = window.acquireChart(this.config); - var meta = chart.getDatasetMeta(0); - var xScale = chart.scales[meta.xAxisID]; - var options = Chart.defaults.controllers.bar.datasets; - - var categoryPercentage = options.categoryPercentage; - var barPercentage = options.barPercentage; - var stacked = xScale.options.stacked; - - var totalBarWidth = 0; - for (var i = 0; i < chart.data.datasets.length; i++) { - var bars = chart.getDatasetMeta(i).data; - for (var j = xScale.min; j <= xScale.max; j++) { - totalBarWidth += bars[j].width; - } - if (stacked) { - break; - } - } - - var actualValue = totalBarWidth; - var expectedValue = xScale.width * categoryPercentage * barPercentage; - expect(actualValue).toBeCloseToPixel(expectedValue); - - }); - - it('should correctly set bar width when min and max option is set.', function() { - this.config = { - type: 'bar', - data: this.data, - options: { - scales: { - x: { - min: 'March', - max: 'May', - } - } - } - }; - }); - - it('should correctly set bar width when scale are stacked with min and max options.', function() { - this.config = { - type: 'bar', - data: this.data, - options: { - scales: { - x: { - min: 'March', - max: 'May', - }, - y: { - stacked: true - } - } - } - }; - }); - }); - - describe('Bar height (horizontal type)', function() { - beforeEach(function() { - // 2 datasets - this.data = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [{ - data: [10, 20, 30, 40, 50, 60, 70], - }, { - data: [10, 20, 30, 40, 50, 60, 70], - }] - }; - }); - - afterEach(function() { - var chart = window.acquireChart(this.config); - var meta = chart.getDatasetMeta(0); - var yScale = chart.scales[meta.yAxisID]; - - var config = meta.controller._config; - var categoryPercentage = config.categoryPercentage; - var barPercentage = config.barPercentage; - var stacked = yScale.options.stacked; - - var totalBarHeight = 0; - for (var i = 0; i < chart.data.datasets.length; i++) { - var bars = chart.getDatasetMeta(i).data; - for (var j = yScale.min; j <= yScale.max; j++) { - totalBarHeight += bars[j].height; - } - if (stacked) { - break; - } - } - - var actualValue = totalBarHeight; - var expectedValue = yScale.height * categoryPercentage * barPercentage; - expect(actualValue).toBeCloseToPixel(expectedValue); - - }); - - it('should correctly set bar height when min and max option is set.', function() { - this.config = { - type: 'bar', - data: this.data, - options: { - indexAxis: 'y', - scales: { - y: { - min: 'March', - max: 'May', - } - } - } - }; - }); - - it('should correctly set bar height when scale are stacked with min and max options.', function() { - this.config = { - type: 'bar', - data: this.data, - options: { - indexAxis: 'y', - scales: { - x: { - stacked: true - }, - y: { - min: 'March', - max: 'May', - } - } - } - }; - }); - }); - - describe('Bar thickness with a category scale', function() { - [undefined, 20].forEach(function(barThickness) { - describe('When barThickness is ' + barThickness, function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2] - }, { - data: [1, 2] - }], - labels: ['label1', 'label2', 'label3'] - }, - options: { - legend: false, - title: false, - datasets: { - bar: { - barThickness: barThickness - } - }, - scales: { - x: { - id: 'x', - type: 'category', - }, - y: { - type: 'linear', - } - } - } - }); - }); - - it('should correctly set bar width', function() { - var chart = this.chart; - var expected, i, ilen, meta; - - if (barThickness) { - expected = barThickness; - } else { - var scale = chart.scales.x; - var options = Chart.defaults.controllers.bar.datasets; - var categoryPercentage = options.categoryPercentage; - var barPercentage = options.barPercentage; - var tickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); - - expected = tickInterval * categoryPercentage / 2 * barPercentage; - } - - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - expect(meta.data[0].width).toBeCloseToPixel(expected); - expect(meta.data[1].width).toBeCloseToPixel(expected); - } - }); - - it('should correctly set bar width if maxBarThickness is specified', function() { - var chart = this.chart; - var i, ilen, meta; - - chart.data.datasets[0].maxBarThickness = 10; - chart.data.datasets[1].maxBarThickness = 10; - chart.update(); - - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - expect(meta.data[0].width).toBeCloseToPixel(10); - expect(meta.data[1].width).toBeCloseToPixel(10); - } - }); - }); - }); - }); - - it('minBarLength settings should be used on Y axis on bar chart', function() { - var minBarLength = 4; - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - minBarLength: minBarLength, - data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] - }] - } - }); - - var data = chart.getDatasetMeta(0).data; - - expect(data[0].base - minBarLength).toEqual(data[0].y); - expect(data[1].base + minBarLength).toEqual(data[1].y); - }); - - it('minBarLength settings should be used on X axis on horizontal bar chart', function() { - var minBarLength = 4; - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - indexAxis: 'y', - minBarLength: minBarLength, - data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] - }] - } - }); - - var data = chart.getDatasetMeta(0).data; - - expect(data[0].base + minBarLength).toEqual(data[0].x); - expect(data[1].base - minBarLength).toEqual(data[1].x); - }); - - it('should respect the data visibility settings', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 2, 3, 4] - }], - labels: ['A', 'B', 'C', 'D'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false, - } - } - } - }); - - var data = chart.getDatasetMeta(0).data; - expect(data[0].base).toBeCloseToPixel(512); - expect(data[0].y).toBeCloseToPixel(384); - - chart.toggleDataVisibility(0); - chart.update(); - - data = chart.getDatasetMeta(0).data; - expect(data[0].base).toBeCloseToPixel(512); - expect(data[0].y).toBeCloseToPixel(512); - }); - - describe('Float bar', function() { - it('Should return correct values from getMinMax', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - labels: ['a'], - datasets: [{ - data: [[10, -10]] - }] - } - }); - - expect(chart.scales.y.getMinMax()).toEqual({min: -10, max: 10}); - }); - }); + describe('auto', jasmine.fixture.specs('controller.bar')); + + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.bar).toBe('function'); + }); + + it('should be constructed', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.type).toEqual('bar'); + expect(meta.data).toEqual([]); + expect(meta.hidden).toBe(null); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(1); + expect(meta.xAxisID).not.toBe(null); + expect(meta.yAxisID).not.toBe(null); + + meta.controller.updateIndex(0); + expect(meta.controller.index).toBe(0); + }); + + it('should use the first scale IDs if the dataset does not specify them', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []} + ], + labels: [] + }, + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.xAxisID).toBe('x'); + expect(meta.yAxisID).toBe('y'); + }); + + it('should correctly count the number of stacks ignoring datasets of other types and hidden datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], type: 'line'}, + {data: [], hidden: true}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackCount()).toBe(2); + }); + + it('should correctly count the number of stacks when a group is not specified', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackCount()).toBe(4); + }); + + it('should correctly count the number of stacks when a group is not specified and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackCount()).toBe(1); + }); + + it('should correctly count the number of stacks when a group is not specified and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackCount()).toBe(4); + }); + + it('should correctly count the number of stacks when a group is specified for some', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(3); + }); + + it('should correctly count the number of stacks when a group is specified for some and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(2); + }); + + it('should correctly count the number of stacks when a group is specified for some and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(4); + }); + + it('should correctly count the number of stacks when a group is specified for all', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(2); + }); + + it('should correctly count the number of stacks when a group is specified for all and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(2); + }); + + it('should correctly count the number of stacks when a group is specified for all and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller._getStackCount()).toBe(4); + }); + + it('should correctly get the stack index accounting for datasets of other types and hidden datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: [], hidden: true}, + {data: [], type: 'line'}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(3)).toBe(1); + }); + + it('should correctly get the stack index when a group is not specified', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(1); + expect(meta.controller._getStackIndex(2)).toBe(2); + expect(meta.controller._getStackIndex(3)).toBe(3); + }); + + it('should correctly get the stack index when a group is not specified and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(0); + expect(meta.controller._getStackIndex(2)).toBe(0); + expect(meta.controller._getStackIndex(3)).toBe(0); + }); + + it('should correctly get the stack index when a group is not specified and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(1); + expect(meta.controller._getStackIndex(2)).toBe(2); + expect(meta.controller._getStackIndex(3)).toBe(3); + }); + + it('should correctly get the stack index when a group is specified for some', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(0); + expect(meta.controller._getStackIndex(2)).toBe(1); + expect(meta.controller._getStackIndex(3)).toBe(2); + }); + + it('should correctly get the stack index when a group is specified for some and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(0); + expect(meta.controller._getStackIndex(2)).toBe(1); + expect(meta.controller._getStackIndex(3)).toBe(1); + }); + + it('should correctly get the stack index when a group is specified for some and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(1); + expect(meta.controller._getStackIndex(2)).toBe(2); + expect(meta.controller._getStackIndex(3)).toBe(3); + }); + + it('should correctly get the stack index when a group is specified for all', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(0); + expect(meta.controller._getStackIndex(2)).toBe(1); + expect(meta.controller._getStackIndex(3)).toBe(1); + }); + + it('should correctly get the stack index when a group is specified for all and the scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(0); + expect(meta.controller._getStackIndex(2)).toBe(1); + expect(meta.controller._getStackIndex(3)).toBe(1); + }); + + it('should correctly get the stack index when a group is specified for all and the scale is not stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + x: { + stacked: false + }, + y: { + stacked: false + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller._getStackIndex(0)).toBe(0); + expect(meta.controller._getStackIndex(1)).toBe(1); + expect(meta.controller._getStackIndex(2)).toBe(2); + expect(meta.controller._getStackIndex(3)).toBe(3); + }); + + it('should create bar elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: []}, + {data: [10, 15, 0, -4]} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.data.length).toBe(4); // 4 bars created + expect(meta.data[0] instanceof Chart.elements.BarElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.BarElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.BarElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.BarElement).toBe(true); + }); + + it('should update elements when modifying data', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + label: 'dataset1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2', + borderColor: 'blue' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + elements: { + bar: { + backgroundColor: 'red', + borderSkipped: 'top', + borderColor: 'green', + borderWidth: 2, + } + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + beginAtZero: false + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.data.length).toBe(4); + + chart.data.datasets[1].data = [1, 2]; // remove 2 items + chart.data.datasets[1].borderWidth = 1; + chart.update(); + + expect(meta.data.length).toBe(2); + expect(meta._parsed.length).toBe(2); + + [ + {x: 89, y: 512}, + {x: 217, y: 0} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].base).toBeCloseToPixel(1024); + expect(meta.data[i].width).toBeCloseToPixel(46); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'red', + borderSkipped: 'top', + borderColor: 'blue', + borderWidth: 1 + })); + }); + + chart.data.datasets[1].data = [1, 2, 3]; // add 1 items + chart.update(); + + expect(meta.data.length).toBe(3); // should add a new meta data item + }); + + it('should get the correct bar points when datasets of different types exist', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + label: 'dataset1' + }, { + type: 'line', + data: [4, 6], + label: 'dataset2' + }, { + data: [8, 10], + label: 'dataset3' + }], + labels: ['label1', 'label2'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + beginAtZero: false + } + } + } + }); + + var meta = chart.getDatasetMeta(2); + expect(meta.data.length).toBe(2); + + var bar1 = meta.data[0]; + var bar2 = meta.data[1]; + + expect(bar1.x).toBeCloseToPixel(179); + expect(bar1.y).toBeCloseToPixel(114); + expect(bar2.x).toBeCloseToPixel(435); + expect(bar2.y).toBeCloseToPixel(0); + }); + + it('should get the bar points for hidden dataset', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + label: 'dataset1', + hidden: true + }], + labels: ['label1', 'label2'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + min: 0, + max: 2, + display: false + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(2); + + var bar1 = meta.data[0]; + var bar2 = meta.data[1]; + + expect(bar1.x).toBeCloseToPixel(128); + expect(bar1.y).toBeCloseToPixel(256); + expect(bar2.x).toBeCloseToPixel(384); + expect(bar2.y).toBeCloseToPixel(0); + }); + + + it('should update elements when the scales are stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [10, -10, 10, -10], + label: 'dataset1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 293, w: 92 / 2, x: 38, y: 146}, + {b: 293, w: 92 / 2, x: 166, y: 439}, + {b: 293, w: 92 / 2, x: 295, y: 146}, + {b: 293, w: 92 / 2, x: 422, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].base).toBeCloseToPixel(values.b); + expect(meta0.data[i].width).toBeCloseToPixel(values.w); + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {b: 146, w: 92 / 2, x: 89, y: 0}, + {b: 293, w: 92 / 2, x: 217, y: 73}, + {b: 146, w: 92 / 2, x: 345, y: 146}, + {b: 439, w: 92 / 2, x: 473, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].base).toBeCloseToPixel(values.b); + expect(meta1.data[i].width).toBeCloseToPixel(values.w); + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should update elements when the scales are stacked and the y axis has a user defined minimum', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [50, 20, 10, 100], + label: 'dataset1' + }, { + data: [50, 80, 90, 0], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true, + min: 50, + max: 100 + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 1024, w: 92 / 2, x: 38, y: 512}, + {b: 1024, w: 92 / 2, x: 166, y: 819}, + {b: 1024, w: 92 / 2, x: 294, y: 922}, + {b: 1024, w: 92 / 2, x: 422.5, y: 0} + ].forEach(function(values, i) { + expect(meta0.data[i].base).toBeCloseToPixel(values.b); + expect(meta0.data[i].width).toBeCloseToPixel(values.w); + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {b: 512, w: 92 / 2, x: 89, y: 0}, + {b: 819.2, w: 92 / 2, x: 217, y: 0}, + {b: 921.6, w: 92 / 2, x: 345, y: 0}, + {b: 0, w: 92 / 2, x: 473.5, y: 0} + ].forEach(function(values, i) { + expect(meta1.data[i].base).toBeCloseToPixel(values.b); + expect(meta1.data[i].width).toBeCloseToPixel(values.w); + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should update elements when only the category scale is stacked', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [20, -10, 10, -10], + label: 'dataset1' + }, { + data: [10, 15, 0, -14], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false, + stacked: true + }, + y: { + type: 'linear', + display: false + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 293, w: 92, x: 64, y: 0}, + {b: 293, w: 92, x: 192, y: 439}, + {b: 293, w: 92, x: 320, y: 146}, + {b: 293, w: 92, x: 448, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].base).toBeCloseToPixel(values.b); + expect(meta0.data[i].width).toBeCloseToPixel(values.w); + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {b: 293, w: 92, x: 64, y: 146}, + {b: 293, w: 92, x: 192, y: 73}, + {b: 293, w: 92, x: 320, y: 293}, + {b: 293, w: 92, x: 448, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].base).toBeCloseToPixel(values.b); + expect(meta1.data[i].width).toBeCloseToPixel(values.w); + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should update elements when the scales are stacked and data is strings', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: ['10', '-10', '10', '-10'], + label: 'dataset1' + }, { + data: ['10', '15', '0', '-4'], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 293, w: 92 / 2, x: 38, y: 146}, + {b: 293, w: 92 / 2, x: 166, y: 439}, + {b: 293, w: 92 / 2, x: 295, y: 146}, + {b: 293, w: 92 / 2, x: 422, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].base).toBeCloseToPixel(values.b); + expect(meta0.data[i].width).toBeCloseToPixel(values.w); + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {b: 146, w: 92 / 2, x: 89, y: 0}, + {b: 293, w: 92 / 2, x: 217, y: 73}, + {b: 146, w: 92 / 2, x: 345, y: 146}, + {b: 439, w: 92 / 2, x: 473, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].base).toBeCloseToPixel(values.b); + expect(meta1.data[i].width).toBeCloseToPixel(values.w); + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should get the correct bar points for grouped stacked chart if the group name is same', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [10, -10, 10, -10], + label: 'dataset1', + stack: 'stack1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2', + stack: 'stack1' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 293, w: 92, x: 64, y: 146}, + {b: 293, w: 92, x: 192, y: 439}, + {b: 293, w: 92, x: 320, y: 146}, + {b: 293, w: 92, x: 448, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].base).toBeCloseToPixel(values.b); + expect(meta0.data[i].width).toBeCloseToPixel(values.w); + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta = chart.getDatasetMeta(1); + + [ + {b: 146, w: 92, x: 64, y: 0}, + {b: 293, w: 92, x: 192, y: 73}, + {b: 146, w: 92, x: 320, y: 146}, + {b: 439, w: 92, x: 448, y: 497} + ].forEach(function(values, i) { + expect(meta.data[i].base).toBeCloseToPixel(values.b); + expect(meta.data[i].width).toBeCloseToPixel(values.w); + expect(meta.data[i].x).toBeCloseToPixel(values.x); + expect(meta.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should get the correct bar points for grouped stacked chart if the group name is different', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + stack: 'stack1' + }, { + data: [1, 2], + stack: 'stack2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true, + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + + [ + {x: 89, y: 256}, + {x: 217, y: 0} + ].forEach(function(values, i) { + expect(meta.data[i].base).toBeCloseToPixel(512); + expect(meta.data[i].width).toBeCloseToPixel(46); + expect(meta.data[i].x).toBeCloseToPixel(values.x); + expect(meta.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should get the correct bar points for grouped stacked chart', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + stack: 'stack1' + }, { + data: [0.5, 1], + stack: 'stack2' + }, { + data: [0.5, 1], + stack: 'stack2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + stacked: true + } + } + } + }); + + var meta = chart.getDatasetMeta(2); + + [ + {b: 384, x: 89, y: 256}, + {b: 256, x: 217, y: 0} + ].forEach(function(values, i) { + expect(meta.data[i].base).toBeCloseToPixel(values.b); + expect(meta.data[i].width).toBeCloseToPixel(46); + expect(meta.data[i].x).toBeCloseToPixel(values.x); + expect(meta.data[i].y).toBeCloseToPixel(values.y); + }); + }); + + it('should draw all bars', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [], + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(1); + + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it('should set hover styles on bars', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [], + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + elements: { + bar: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + var bar = meta.data[0]; + + meta.controller.setHoverStyle(bar, 1, 0); + expect(bar.options.backgroundColor).toBe('#E60000'); + expect(bar.options.borderColor).toBe('#0000E6'); + expect(bar.options.borderWidth).toBe(2); + + // Set a dataset style + chart.data.datasets[1].hoverBackgroundColor = 'rgb(128, 128, 128)'; + chart.data.datasets[1].hoverBorderColor = 'rgb(0, 0, 0)'; + chart.data.datasets[1].hoverBorderWidth = 5; + chart.update(); + + meta.controller.setHoverStyle(bar, 1, 0); + expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); + expect(bar.options.borderColor).toBe('rgb(0, 0, 0)'); + expect(bar.options.borderWidth).toBe(5); + + // Should work with array styles so that we can set per bar + chart.data.datasets[1].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)']; + chart.data.datasets[1].hoverBorderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)']; + chart.data.datasets[1].hoverBorderWidth = [2.5, 5]; + chart.update(); + + meta.controller.setHoverStyle(bar, 1, 0); + expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); + expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); + expect(bar.options.borderWidth).toBe(2.5); + }); + + it('should remove a hover style from a bar', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [], + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + elements: { + bar: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2, + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + var bar = meta.data[0]; + var helpers = window.Chart.helpers; + + // Change default + chart.options.elements.bar.backgroundColor = 'rgb(128, 128, 128)'; + chart.options.elements.bar.borderColor = 'rgb(15, 15, 15)'; + chart.options.elements.bar.borderWidth = 3.14; + + chart.update(); + expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); + expect(bar.options.borderColor).toBe('rgb(15, 15, 15)'); + expect(bar.options.borderWidth).toBe(3.14); + meta.controller.setHoverStyle(bar, 1, 0); + expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(128, 128, 128)')); + expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(15, 15, 15)')); + expect(bar.options.borderWidth).toBe(3.14); + meta.controller.removeHoverStyle(bar); + expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)'); + expect(bar.options.borderColor).toBe('rgb(15, 15, 15)'); + expect(bar.options.borderWidth).toBe(3.14); + + // Should work with array styles so that we can set per bar + chart.data.datasets[1].backgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)']; + chart.data.datasets[1].borderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)']; + chart.data.datasets[1].borderWidth = [2.5, 5]; + + chart.update(); + expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); + expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); + expect(bar.options.borderWidth).toBe(2.5); + meta.controller.setHoverStyle(bar, 1, 0); + expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(255, 255, 255)')); + expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(9, 9, 9)')); + expect(bar.options.borderWidth).toBe(2.5); + meta.controller.removeHoverStyle(bar); + expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)'); + expect(bar.options.borderColor).toBe('rgb(9, 9, 9)'); + expect(bar.options.borderWidth).toBe(2.5); + }); + + describe('Bar width', function() { + beforeEach(function() { + // 2 datasets + this.data = { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + data: [10, 20, 30, 40, 50, 60, 70], + }, { + data: [10, 20, 30, 40, 50, 60, 70], + }] + }; + }); + + afterEach(function() { + var chart = window.acquireChart(this.config); + var meta = chart.getDatasetMeta(0); + var xScale = chart.scales[meta.xAxisID]; + var options = Chart.defaults.controllers.bar.datasets; + + var categoryPercentage = options.categoryPercentage; + var barPercentage = options.barPercentage; + var stacked = xScale.options.stacked; + + var totalBarWidth = 0; + for (var i = 0; i < chart.data.datasets.length; i++) { + var bars = chart.getDatasetMeta(i).data; + for (var j = xScale.min; j <= xScale.max; j++) { + totalBarWidth += bars[j].width; + } + if (stacked) { + break; + } + } + + var actualValue = totalBarWidth; + var expectedValue = xScale.width * categoryPercentage * barPercentage; + expect(actualValue).toBeCloseToPixel(expectedValue); + + }); + + it('should correctly set bar width when min and max option is set.', function() { + this.config = { + type: 'bar', + data: this.data, + options: { + scales: { + x: { + min: 'March', + max: 'May', + } + } + } + }; + }); + + it('should correctly set bar width when scale are stacked with min and max options.', function() { + this.config = { + type: 'bar', + data: this.data, + options: { + scales: { + x: { + min: 'March', + max: 'May', + }, + y: { + stacked: true + } + } + } + }; + }); + }); + + describe('Bar height (horizontal type)', function() { + beforeEach(function() { + // 2 datasets + this.data = { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + data: [10, 20, 30, 40, 50, 60, 70], + }, { + data: [10, 20, 30, 40, 50, 60, 70], + }] + }; + }); + + afterEach(function() { + var chart = window.acquireChart(this.config); + var meta = chart.getDatasetMeta(0); + var yScale = chart.scales[meta.yAxisID]; + + var config = meta.controller._config; + var categoryPercentage = config.categoryPercentage; + var barPercentage = config.barPercentage; + var stacked = yScale.options.stacked; + + var totalBarHeight = 0; + for (var i = 0; i < chart.data.datasets.length; i++) { + var bars = chart.getDatasetMeta(i).data; + for (var j = yScale.min; j <= yScale.max; j++) { + totalBarHeight += bars[j].height; + } + if (stacked) { + break; + } + } + + var actualValue = totalBarHeight; + var expectedValue = yScale.height * categoryPercentage * barPercentage; + expect(actualValue).toBeCloseToPixel(expectedValue); + + }); + + it('should correctly set bar height when min and max option is set.', function() { + this.config = { + type: 'bar', + data: this.data, + options: { + indexAxis: 'y', + scales: { + y: { + min: 'March', + max: 'May', + } + } + } + }; + }); + + it('should correctly set bar height when scale are stacked with min and max options.', function() { + this.config = { + type: 'bar', + data: this.data, + options: { + indexAxis: 'y', + scales: { + x: { + stacked: true + }, + y: { + min: 'March', + max: 'May', + } + } + } + }; + }); + }); + + describe('Bar thickness with a category scale', function() { + [undefined, 20].forEach(function(barThickness) { + describe('When barThickness is ' + barThickness, function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2] + }, { + data: [1, 2] + }], + labels: ['label1', 'label2', 'label3'] + }, + options: { + legend: false, + title: false, + datasets: { + bar: { + barThickness: barThickness + } + }, + scales: { + x: { + id: 'x', + type: 'category', + }, + y: { + type: 'linear', + } + } + } + }); + }); + + it('should correctly set bar width', function() { + var chart = this.chart; + var expected, i, ilen, meta; + + if (barThickness) { + expected = barThickness; + } else { + var scale = chart.scales.x; + var options = Chart.defaults.controllers.bar.datasets; + var categoryPercentage = options.categoryPercentage; + var barPercentage = options.barPercentage; + var tickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); + + expected = tickInterval * categoryPercentage / 2 * barPercentage; + } + + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + expect(meta.data[0].width).toBeCloseToPixel(expected); + expect(meta.data[1].width).toBeCloseToPixel(expected); + } + }); + + it('should correctly set bar width if maxBarThickness is specified', function() { + var chart = this.chart; + var i, ilen, meta; + + chart.data.datasets[0].maxBarThickness = 10; + chart.data.datasets[1].maxBarThickness = 10; + chart.update(); + + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + expect(meta.data[0].width).toBeCloseToPixel(10); + expect(meta.data[1].width).toBeCloseToPixel(10); + } + }); + }); + }); + }); + + it('minBarLength settings should be used on Y axis on bar chart', function() { + var minBarLength = 4; + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + minBarLength: minBarLength, + data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] + }] + } + }); + + var data = chart.getDatasetMeta(0).data; + + expect(data[0].base - minBarLength).toEqual(data[0].y); + expect(data[1].base + minBarLength).toEqual(data[1].y); + }); + + it('minBarLength settings should be used on X axis on horizontal bar chart', function() { + var minBarLength = 4; + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + indexAxis: 'y', + minBarLength: minBarLength, + data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] + }] + } + }); + + var data = chart.getDatasetMeta(0).data; + + expect(data[0].base + minBarLength).toEqual(data[0].x); + expect(data[1].base - minBarLength).toEqual(data[1].x); + }); + + it('should respect the data visibility settings', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2, 3, 4] + }], + labels: ['A', 'B', 'C', 'D'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false, + } + } + } + }); + + var data = chart.getDatasetMeta(0).data; + expect(data[0].base).toBeCloseToPixel(512); + expect(data[0].y).toBeCloseToPixel(384); + + chart.toggleDataVisibility(0); + chart.update(); + + data = chart.getDatasetMeta(0).data; + expect(data[0].base).toBeCloseToPixel(512); + expect(data[0].y).toBeCloseToPixel(512); + }); + + describe('Float bar', function() { + it('Should return correct values from getMinMax', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + labels: ['a'], + datasets: [{ + data: [[10, -10]] + }] + } + }); + + expect(chart.scales.y.getMinMax()).toEqual({min: -10, max: 10}); + }); + }); }); diff --git a/test/specs/controller.bubble.tests.js b/test/specs/controller.bubble.tests.js index 8a886dbca94..0a498bce665 100644 --- a/test/specs/controller.bubble.tests.js +++ b/test/specs/controller.bubble.tests.js @@ -1,381 +1,381 @@ 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', - data: { - datasets: [{ - data: [] - }] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('bubble'); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(0); - expect(meta.data).toEqual([]); - - meta.controller.updateIndex(1); - expect(meta.controller.index).toBe(1); - }); - - it('should use the first scale IDs if the dataset does not specify them', function() { - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - data: [] - }] - }, - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.xAxisID).toBe('x'); - expect(meta.yAxisID).toBe('y'); - }); - - it('should create point elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - data: [10, 15, 0, -4] - }] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(4); // 4 points created - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); - }); - - it('should draw all elements', function() { - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - data: [10, 15, 0, -4] - }] - }, - options: { - animation: false, - showLine: true - } - }); - - var meta = chart.getDatasetMeta(0); - - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it('should update elements when modifying style', function() { - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - data: [{ - x: 10, - y: 10, - r: 5 - }, { - x: -15, - y: -10, - r: 1 - }, { - x: 0, - y: -9, - r: 2 - }, { - x: -4, - y: 10, - r: 1 - }] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - - [ - {r: 5, x: 0, y: 0}, - {r: 1, x: 171, y: 512}, - {r: 2, x: 341, y: 486}, - {r: 1, x: 512, y: 0} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: Chart.defaults.backgroundColor, - borderColor: Chart.defaults.borderColor, - borderWidth: 1, - hitRadius: 1, - radius: expected.r - })); - }); - - // Use dataset level styles for lines & points - chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)'; - chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)'; - chart.data.datasets[0].borderWidth = 0.55; - - // point styles - chart.data.datasets[0].radius = 22; - chart.data.datasets[0].hitRadius = 3.3; - - chart.update(); - - for (var i = 0; i < 4; ++i) { - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(98, 98, 98)', - borderColor: 'rgb(8, 8, 8)', - borderWidth: 0.55, - hitRadius: 3.3 - })); - } - }); - - it('should handle number of data point changes in update', function() { - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - data: [{ - x: 10, - y: 10, - r: 5 - }, { - x: -15, - y: -10, - r: 1 - }, { - x: 0, - y: -9, - r: 2 - }, { - x: -4, - y: 10, - r: 1 - }] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(4); - - chart.data.datasets[0].data = [{ - x: 1, - y: 1, - r: 10 - }, { - x: 10, - y: 5, - r: 2 - }]; // remove 2 items - - chart.update(); - - expect(meta.data.length).toBe(2); - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - - chart.data.datasets[0].data = [{ - x: 10, - y: 10, - r: 5 - }, { - x: -15, - y: -10, - r: 1 - }, { - x: 0, - y: -9, - r: 2 - }, { - x: -4, - y: 10, - r: 1 - }, { - x: -5, - y: 0, - r: 3 - }]; // add 3 items - - chart.update(); - - expect(meta.data.length).toBe(5); - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true); - }); - - describe('Interactions', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'bubble', - data: { - labels: ['label1', 'label2', 'label3', 'label4'], - datasets: [{ - data: [{ - x: 5, - y: 5, - r: 20 - }, { - x: -15, - y: -10, - r: 15 - }, { - x: 15, - y: 10, - r: 10 - }, { - x: -15, - y: 10, - r: 5 - }] - }] - }, - options: { - elements: { - point: { - backgroundColor: 'rgb(100, 150, 200)', - borderColor: 'rgb(50, 100, 150)', - borderWidth: 2, - radius: 3 - } - } - } - }); - }); - - it ('should handle default hover styles', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('#3187DD'); - expect(point.options.borderColor).toBe('#175A9D'); - expect(point.options.borderWidth).toBe(1); - expect(point.options.radius).toBe(20 + 4); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(20); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.data.datasets[0], { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(20 + 4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(20); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via element options', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.options.elements.point, { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(20 + 4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(20); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); + 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', + data: { + datasets: [{ + data: [] + }] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('bubble'); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(0); + expect(meta.data).toEqual([]); + + meta.controller.updateIndex(1); + expect(meta.controller.index).toBe(1); + }); + + it('should use the first scale IDs if the dataset does not specify them', function() { + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + data: [] + }] + }, + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.xAxisID).toBe('x'); + expect(meta.yAxisID).toBe('y'); + }); + + it('should create point elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(4); // 4 points created + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); + }); + + it('should draw all elements', function() { + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + options: { + animation: false, + showLine: true + } + }); + + var meta = chart.getDatasetMeta(0); + + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it('should update elements when modifying style', function() { + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + data: [{ + x: 10, + y: 10, + r: 5 + }, { + x: -15, + y: -10, + r: 1 + }, { + x: 0, + y: -9, + r: 2 + }, { + x: -4, + y: 10, + r: 1 + }] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + + [ + {r: 5, x: 0, y: 0}, + {r: 1, x: 171, y: 512}, + {r: 2, x: 341, y: 486}, + {r: 1, x: 512, y: 0} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: Chart.defaults.backgroundColor, + borderColor: Chart.defaults.borderColor, + borderWidth: 1, + hitRadius: 1, + radius: expected.r + })); + }); + + // Use dataset level styles for lines & points + chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)'; + chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)'; + chart.data.datasets[0].borderWidth = 0.55; + + // point styles + chart.data.datasets[0].radius = 22; + chart.data.datasets[0].hitRadius = 3.3; + + chart.update(); + + for (var i = 0; i < 4; ++i) { + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(98, 98, 98)', + borderColor: 'rgb(8, 8, 8)', + borderWidth: 0.55, + hitRadius: 3.3 + })); + } + }); + + it('should handle number of data point changes in update', function() { + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + data: [{ + x: 10, + y: 10, + r: 5 + }, { + x: -15, + y: -10, + r: 1 + }, { + x: 0, + y: -9, + r: 2 + }, { + x: -4, + y: 10, + r: 1 + }] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(4); + + chart.data.datasets[0].data = [{ + x: 1, + y: 1, + r: 10 + }, { + x: 10, + y: 5, + r: 2 + }]; // remove 2 items + + chart.update(); + + expect(meta.data.length).toBe(2); + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + + chart.data.datasets[0].data = [{ + x: 10, + y: 10, + r: 5 + }, { + x: -15, + y: -10, + r: 1 + }, { + x: 0, + y: -9, + r: 2 + }, { + x: -4, + y: 10, + r: 1 + }, { + x: -5, + y: 0, + r: 3 + }]; // add 3 items + + chart.update(); + + expect(meta.data.length).toBe(5); + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true); + }); + + describe('Interactions', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'bubble', + data: { + labels: ['label1', 'label2', 'label3', 'label4'], + datasets: [{ + data: [{ + x: 5, + y: 5, + r: 20 + }, { + x: -15, + y: -10, + r: 15 + }, { + x: 15, + y: 10, + r: 10 + }, { + x: -15, + y: 10, + r: 5 + }] + }] + }, + options: { + elements: { + point: { + backgroundColor: 'rgb(100, 150, 200)', + borderColor: 'rgb(50, 100, 150)', + borderWidth: 2, + radius: 3 + } + } + } + }); + }); + + it ('should handle default hover styles', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('#3187DD'); + expect(point.options.borderColor).toBe('#175A9D'); + expect(point.options.borderWidth).toBe(1); + expect(point.options.radius).toBe(20 + 4); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(20); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.data.datasets[0], { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(20 + 4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(20); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via element options', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.options.elements.point, { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(20 + 4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(20); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); }); diff --git a/test/specs/controller.doughnut.tests.js b/test/specs/controller.doughnut.tests.js index a2693cde217..16f0b740137 100644 --- a/test/specs/controller.doughnut.tests.js +++ b/test/specs/controller.doughnut.tests.js @@ -1,419 +1,419 @@ describe('Chart.controllers.doughnut', function() { - describe('auto', jasmine.fixture.specs('controller.doughnut')); - - it('should be registered as dataset controller', function() { - expect(typeof Chart.controllers.doughnut).toBe('function'); - expect(typeof Chart.controllers.pie).toBe('function'); - }); - - it('should be constructed', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [] - }], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('doughnut'); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(0); - expect(meta.data).toEqual([]); - - meta.controller.updateIndex(1); - expect(meta.controller.index).toBe(1); - }); - - it('should create arc elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [10, 15, 0, 4] - }], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); // 4 arcs created - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); - }); - - it ('should reset and update elements', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [1, 2, 3, 4], - hidden: true - }, { - data: [5, 6, 0, 7] - }, { - data: [8, 9, 10, 11] - }], - labels: ['label0', 'label1', 'label2', 'label3'] - }, - options: { - plugins: { - legend: false, - title: false, - }, - animation: { - duration: 0, - animateRotate: true, - animateScale: false - }, - cutoutPercentage: 50, - rotation: 0, - circumference: 360, - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2 - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - - meta.controller.reset(); // reset first - - expect(meta.data.length).toBe(4); - - [ - {c: 0}, - {c: 0}, - {c: 0}, - {c: 0} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(256); - expect(meta.data[i].y).toBeCloseToPixel(256); - expect(meta.data[i].outerRadius).toBeCloseToPixel(256); - expect(meta.data[i].innerRadius).toBeCloseToPixel(192); - expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); - expect(meta.data[i].startAngle).toBeCloseToPixel(Math.PI * -0.5); - expect(meta.data[i].endAngle).toBeCloseToPixel(Math.PI * -0.5); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2 - })); - }); - - chart.update(); - - [ - {c: 1.7453292519, s: -1.5707963267, e: 0.1745329251}, - {c: 2.0943951023, s: 0.1745329251, e: 2.2689280275}, - {c: 0, s: 2.2689280275, e: 2.2689280275}, - {c: 2.4434609527, s: 2.2689280275, e: 4.7123889803} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(256); - expect(meta.data[i].y).toBeCloseToPixel(256); - expect(meta.data[i].outerRadius).toBeCloseToPixel(256); - expect(meta.data[i].innerRadius).toBeCloseToPixel(192); - expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); - expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); - expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2 - })); - }); - - // Change the amount of data and ensure that arcs are updated accordingly - chart.data.datasets[1].data = [1, 2]; // remove 2 elements from dataset 0 - chart.update(); - - expect(meta.data.length).toBe(2); - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - - // Add data - chart.data.datasets[1].data = [1, 2, 3, 4]; - chart.update(); - - expect(meta.data.length).toBe(4); - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); - }); - - it ('should rotate and limit circumference', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [2, 4], - hidden: true - }, { - data: [1, 3] - }, { - data: [1, 0] - }], - labels: ['label0', 'label1', 'label2'] - }, - options: { - plugins: { - legend: false, - title: false, - }, - cutoutPercentage: 50, - rotation: 270, - circumference: 90, - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2 - } - } - } - }); - - var meta = chart.getDatasetMeta(1); - - expect(meta.data.length).toBe(2); - - // Only startAngle, endAngle and circumference should be different. - [ - {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8}, - {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(512); - expect(meta.data[i].y).toBeCloseToPixel(512); - expect(meta.data[i].outerRadius).toBeCloseToPixel(512); - expect(meta.data[i].innerRadius).toBeCloseToPixel(384); - expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); - expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); - expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); - }); - }); - - it('should treat negative values as positive', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [-1, -3] - }], - labels: ['label0', 'label1'] - }, - options: { - plugins: { - legend: false, - title: false - }, - cutoutPercentage: 50, - rotation: 270, - circumference: 90, - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 0, 255)', - borderWidth: 2 - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(2); - - // Only startAngle, endAngle and circumference should be different. - [ - {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8}, - {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2} - ].forEach(function(expected, i) { - expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); - expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); - expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); - }); - }); - - it ('should draw all arcs', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [10, 15, 0, 4] - }], - labels: ['label0', 'label1', 'label2', 'label3'] - } - }); - - var meta = chart.getDatasetMeta(0); - - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it ('should calculate radiuses based on the border widths of the visible outermost dataset', function() { - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - data: [2, 4], - borderWidth: 4, - hidden: true - }, { - data: [1, 3], - borderWidth: 8 - }, { - data: [1, 0], - borderWidth: 12 - }], - labels: ['label0', 'label1'] - }, - options: { - plugins: { - legend: false, - title: false - } - } - }); - - chart.update(); - - var controller = chart.getDatasetMeta(0).controller; - expect(chart.chartArea.bottom - chart.chartArea.top).toBe(512); - - expect(controller.getMaxBorderWidth()).toBe(8); - expect(controller.outerRadius).toBe(252); - expect(controller.innerRadius).toBe(189); - - controller = chart.getDatasetMeta(1).controller; - expect(controller.getMaxBorderWidth()).toBe(8); - expect(controller.outerRadius).toBe(252); - expect(controller.innerRadius).toBe(189); - - controller = chart.getDatasetMeta(2).controller; - expect(controller.getMaxBorderWidth()).toBe(8); - expect(controller.outerRadius).toBe(189); - expect(controller.innerRadius).toBe(126); - }); - - describe('Interactions', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'doughnut', - data: { - labels: ['label1', 'label2', 'label3', 'label4'], - datasets: [{ - data: [10, 15, 0, 4] - }] - }, - options: { - cutoutPercentage: 0, - elements: { - arc: { - backgroundColor: 'rgb(100, 150, 200)', - borderColor: 'rgb(50, 100, 150)', - borderWidth: 2, - } - } - } - }); - }); - - it ('should handle default hover styles', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('#3187DD'); - expect(arc.options.borderColor).toBe('#175A9D'); - expect(arc.options.borderWidth).toBe(2); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - - it ('should handle hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.data.datasets[0], { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(arc.options.borderWidth).toBe(8.4); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - - it ('should handle hover styles defined via element options', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.options.elements.arc, { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(arc.options.borderWidth).toBe(8.4); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - }); + describe('auto', jasmine.fixture.specs('controller.doughnut')); + + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.doughnut).toBe('function'); + expect(typeof Chart.controllers.pie).toBe('function'); + }); + + it('should be constructed', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [] + }], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('doughnut'); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(0); + expect(meta.data).toEqual([]); + + meta.controller.updateIndex(1); + expect(meta.controller.index).toBe(1); + }); + + it('should create arc elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); // 4 arcs created + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); + }); + + it ('should reset and update elements', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [1, 2, 3, 4], + hidden: true + }, { + data: [5, 6, 0, 7] + }, { + data: [8, 9, 10, 11] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + }, + options: { + plugins: { + legend: false, + title: false, + }, + animation: { + duration: 0, + animateRotate: true, + animateScale: false + }, + cutoutPercentage: 50, + rotation: 0, + circumference: 360, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2 + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + + meta.controller.reset(); // reset first + + expect(meta.data.length).toBe(4); + + [ + {c: 0}, + {c: 0}, + {c: 0}, + {c: 0} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(256); + expect(meta.data[i].y).toBeCloseToPixel(256); + expect(meta.data[i].outerRadius).toBeCloseToPixel(256); + expect(meta.data[i].innerRadius).toBeCloseToPixel(192); + expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); + expect(meta.data[i].startAngle).toBeCloseToPixel(Math.PI * -0.5); + expect(meta.data[i].endAngle).toBeCloseToPixel(Math.PI * -0.5); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2 + })); + }); + + chart.update(); + + [ + {c: 1.7453292519, s: -1.5707963267, e: 0.1745329251}, + {c: 2.0943951023, s: 0.1745329251, e: 2.2689280275}, + {c: 0, s: 2.2689280275, e: 2.2689280275}, + {c: 2.4434609527, s: 2.2689280275, e: 4.7123889803} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(256); + expect(meta.data[i].y).toBeCloseToPixel(256); + expect(meta.data[i].outerRadius).toBeCloseToPixel(256); + expect(meta.data[i].innerRadius).toBeCloseToPixel(192); + expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); + expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); + expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2 + })); + }); + + // Change the amount of data and ensure that arcs are updated accordingly + chart.data.datasets[1].data = [1, 2]; // remove 2 elements from dataset 0 + chart.update(); + + expect(meta.data.length).toBe(2); + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + + // Add data + chart.data.datasets[1].data = [1, 2, 3, 4]; + chart.update(); + + expect(meta.data.length).toBe(4); + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); + }); + + it ('should rotate and limit circumference', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [2, 4], + hidden: true + }, { + data: [1, 3] + }, { + data: [1, 0] + }], + labels: ['label0', 'label1', 'label2'] + }, + options: { + plugins: { + legend: false, + title: false, + }, + cutoutPercentage: 50, + rotation: 270, + circumference: 90, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2 + } + } + } + }); + + var meta = chart.getDatasetMeta(1); + + expect(meta.data.length).toBe(2); + + // Only startAngle, endAngle and circumference should be different. + [ + {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8}, + {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(512); + expect(meta.data[i].y).toBeCloseToPixel(512); + expect(meta.data[i].outerRadius).toBeCloseToPixel(512); + expect(meta.data[i].innerRadius).toBeCloseToPixel(384); + expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); + expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); + expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); + }); + }); + + it('should treat negative values as positive', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [-1, -3] + }], + labels: ['label0', 'label1'] + }, + options: { + plugins: { + legend: false, + title: false + }, + cutoutPercentage: 50, + rotation: 270, + circumference: 90, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 0, 255)', + borderWidth: 2 + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(2); + + // Only startAngle, endAngle and circumference should be different. + [ + {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8}, + {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2} + ].forEach(function(expected, i) { + expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8); + expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8); + expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8); + }); + }); + + it ('should draw all arcs', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label0', 'label1', 'label2', 'label3'] + } + }); + + var meta = chart.getDatasetMeta(0); + + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it ('should calculate radiuses based on the border widths of the visible outermost dataset', function() { + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + data: [2, 4], + borderWidth: 4, + hidden: true + }, { + data: [1, 3], + borderWidth: 8 + }, { + data: [1, 0], + borderWidth: 12 + }], + labels: ['label0', 'label1'] + }, + options: { + plugins: { + legend: false, + title: false + } + } + }); + + chart.update(); + + var controller = chart.getDatasetMeta(0).controller; + expect(chart.chartArea.bottom - chart.chartArea.top).toBe(512); + + expect(controller.getMaxBorderWidth()).toBe(8); + expect(controller.outerRadius).toBe(252); + expect(controller.innerRadius).toBe(189); + + controller = chart.getDatasetMeta(1).controller; + expect(controller.getMaxBorderWidth()).toBe(8); + expect(controller.outerRadius).toBe(252); + expect(controller.innerRadius).toBe(189); + + controller = chart.getDatasetMeta(2).controller; + expect(controller.getMaxBorderWidth()).toBe(8); + expect(controller.outerRadius).toBe(189); + expect(controller.innerRadius).toBe(126); + }); + + describe('Interactions', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'doughnut', + data: { + labels: ['label1', 'label2', 'label3', 'label4'], + datasets: [{ + data: [10, 15, 0, 4] + }] + }, + options: { + cutoutPercentage: 0, + elements: { + arc: { + backgroundColor: 'rgb(100, 150, 200)', + borderColor: 'rgb(50, 100, 150)', + borderWidth: 2, + } + } + } + }); + }); + + it ('should handle default hover styles', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('#3187DD'); + expect(arc.options.borderColor).toBe('#175A9D'); + expect(arc.options.borderWidth).toBe(2); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + + it ('should handle hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.data.datasets[0], { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(arc.options.borderWidth).toBe(8.4); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + + it ('should handle hover styles defined via element options', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.options.elements.arc, { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(arc.options.borderWidth).toBe(8.4); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + }); }); diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index afe7e9c3d7d..e7bccf71e7c 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -1,936 +1,936 @@ 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', - data: { - datasets: [{ - data: [] - }], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('line'); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(0); - expect(meta.data).toEqual([]); - - meta.controller.updateIndex(1); - expect(meta.controller.index).toBe(1); - }); - - it('Should use the first scale IDs if the dataset does not specify them', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [] - }], - labels: [] - }, - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.xAxisID).toBe('x'); - expect(meta.yAxisID).toBe('y'); - }); - - it('Should create line elements and point elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); // 4 points created - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // 1 line element - }); - - it('should draw all elements', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true - } - }); - - var meta = chart.getDatasetMeta(0); - spyOn(meta.dataset, 'updateControlPoints'); - spyOn(meta.dataset, 'draw'); - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.dataset.updateControlPoints.calls.count()).toBeGreaterThanOrEqual(1); - expect(meta.dataset.draw.calls.count()).toBe(1); - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it('should update elements when modifying data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset', - xAxisID: 'x', - yAxisID: 'y' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - plugins: { - legend: false, - title: false - }, - elements: { - point: { - backgroundColor: 'red', - borderColor: 'blue', - } - }, - scales: { - x: { - display: false - }, - y: { - display: false - } - } - }, - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); - - chart.data.datasets[0].data = [1, 2]; // remove 2 items - chart.data.datasets[0].borderWidth = 1; - chart.update(); - - expect(meta.data.length).toBe(2); - expect(meta._parsed.length).toBe(2); - - [ - {x: 0, y: 512}, - {x: 171, y: 0} - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'red', - borderColor: 'blue', - })); - }); - - chart.data.datasets[0].data = [1, 2, 3]; // add 1 items - chart.update(); - - expect(meta.data.length).toBe(3); // should add a new meta data item - }); - - it('should correctly calculate x scale for label and point', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['One'], - datasets: [{ - data: [1], - }] - }, - options: { - plugins: { - legend: false, - title: false - }, - hover: { - mode: 'nearest', - intersect: true - }, - scales: { - x: { - display: false, - }, - y: { - display: false, - beginAtZero: true - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - // 1 point - var point = meta.data[0]; - expect(point.x).toBeCloseToPixel(0); - - // 2 points - chart.data.labels = ['One', 'Two']; - chart.data.datasets[0].data = [1, 2]; - chart.update(); - - var points = meta.data; - - expect(points[0].x).toBeCloseToPixel(0); - expect(points[1].x).toBeCloseToPixel(512); - - // 3 points - chart.data.labels = ['One', 'Two', 'Three']; - chart.data.datasets[0].data = [1, 2, 3]; - chart.update(); - - points = meta.data; - - expect(points[0].x).toBeCloseToPixel(0); - expect(points[1].x).toBeCloseToPixel(256); - expect(points[2].x).toBeCloseToPixel(512); - - // 4 points - chart.data.labels = ['One', 'Two', 'Three', 'Four']; - chart.data.datasets[0].data = [1, 2, 3, 4]; - chart.update(); - - points = meta.data; - - expect(points[0].x).toBeCloseToPixel(0); - expect(points[1].x).toBeCloseToPixel(171); - expect(points[2].x).toBeCloseToPixel(340); - expect(points[3].x).toBeCloseToPixel(512); - }); - - it('should update elements when the y scale is stacked', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, -10, 10, -10], - label: 'dataset1' - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - display: false, - }, - y: { - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {x: 0, y: 146}, - {x: 171, y: 439}, - {x: 341, y: 146}, - {x: 512, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {x: 0, y: 0}, - {x: 171, y: 73}, - {x: 341, y: 146}, - {x: 512, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - - }); - - it('should update elements when the y scale is stacked with multiple axes', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, -10, 10, -10], - label: 'dataset1' - }, { - data: [10, 15, 0, -4], - label: 'dataset2' - }, { - data: [10, 10, -10, -10], - label: 'dataset3', - yAxisID: 'y2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false, - }, - scales: { - x: { - display: false, - }, - y: { - display: false, - stacked: true - }, - y2: { - type: 'linear', - position: 'right', - display: false - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {x: 0, y: 146}, - {x: 171, y: 439}, - {x: 341, y: 146}, - {x: 512, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {x: 0, y: 0}, - {x: 171, y: 73}, - {x: 341, y: 146}, - {x: 512, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - - }); - - it('should update elements when the y scale is stacked and datasets is scatter data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [{ - x: 0, - y: 10 - }, { - x: 1, - y: -10 - }, { - x: 2, - y: 10 - }, { - x: 3, - y: -10 - }], - label: 'dataset1' - }, { - data: [{ - x: 0, - y: 10 - }, { - x: 1, - y: 15 - }, { - x: 2, - y: 0 - }, { - x: 3, - y: -4 - }], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - display: false, - }, - y: { - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {x: 0, y: 146}, - {x: 171, y: 439}, - {x: 341, y: 146}, - {x: 512, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {x: 0, y: 0}, - {x: 171, y: 73}, - {x: 341, y: 146}, - {x: 512, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - - }); - - it('should update elements when the y scale is stacked and data is strings', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: ['10', '-10', '10', '-10'], - label: 'dataset1' - }, { - data: ['10', '15', '0', '-4'], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - plugins: { - legend: false, - title: false - }, - scales: { - x: { - display: false, - }, - y: { - display: false, - stacked: true - } - } - } - }); - - var meta0 = chart.getDatasetMeta(0); - - [ - {x: 0, y: 146}, - {x: 171, y: 439}, - {x: 341, y: 146}, - {x: 512, y: 439} - ].forEach(function(values, i) { - expect(meta0.data[i].x).toBeCloseToPixel(values.x); - expect(meta0.data[i].y).toBeCloseToPixel(values.y); - }); - - var meta1 = chart.getDatasetMeta(1); - - [ - {x: 0, y: 0}, - {x: 171, y: 73}, - {x: 341, y: 146}, - {x: 512, y: 497} - ].forEach(function(values, i) { - expect(meta1.data[i].x).toBeCloseToPixel(values.x); - expect(meta1.data[i].y).toBeCloseToPixel(values.y); - }); - - }); - - it('should fall back to the line styles for points', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 0], - label: 'dataset1', - - // line styles - backgroundColor: 'rgb(98, 98, 98)', - borderColor: 'rgb(8, 8, 8)', - borderWidth: 0.55, - }], - labels: ['label1', 'label2'] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.dataset.options.backgroundColor).toBe('rgb(98, 98, 98)'); - expect(meta.dataset.options.borderColor).toBe('rgb(8, 8, 8)'); - expect(meta.dataset.options.borderWidth).toBe(0.55); - }); - - describe('dataset global defaults', function() { - beforeEach(function() { - this._defaults = Chart.helpers.clone(Chart.defaults.controllers.line.datasets); - }); - - afterEach(function() { - Chart.defaults.controllers.line.datasets = this._defaults; - delete this._defaults; - }); - - it('should utilize the dataset global default options', function() { - Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {}; - - Chart.helpers.merge(Chart.defaults.controllers.line.datasets, { - spanGaps: true, - tension: 0.231, - backgroundColor: '#add', - borderWidth: '#daa', - borderColor: '#dad', - borderCapStyle: 'round', - borderDash: [0], - borderDashOffset: 0.871, - borderJoinStyle: 'miter', - fill: 'start', - cubicInterpolationMode: 'monotone' - }); - - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 0], - label: 'dataset1' - }], - labels: ['label1', 'label2'] - } - }); - - var options = chart.getDatasetMeta(0).dataset.options; - - expect(options.spanGaps).toBe(true); - expect(options.tension).toBe(0.231); - expect(options.backgroundColor).toBe('#add'); - expect(options.borderWidth).toBe('#daa'); - expect(options.borderColor).toBe('#dad'); - expect(options.borderCapStyle).toBe('round'); - expect(options.borderDash).toEqual([0]); - expect(options.borderDashOffset).toBe(0.871); - expect(options.borderJoinStyle).toBe('miter'); - expect(options.fill).toBe('start'); - expect(options.cubicInterpolationMode).toBe('monotone'); - }); - - it('should be overriden by user-supplied values', function() { - Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {}; - - Chart.helpers.merge(Chart.defaults.controllers.line.datasets, { - spanGaps: true, - tension: 0.231 - }); - - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 0], - label: 'dataset1', - spanGaps: true, - backgroundColor: '#dad' - }], - labels: ['label1', 'label2'] - }, - options: { - datasets: { - line: { - tension: 0.345, - backgroundColor: '#add' - } - } - } - }); - - var options = chart.getDatasetMeta(0).dataset.options; - - // dataset-level option overrides global default - expect(options.spanGaps).toBe(true); - // chart-level default overrides global default - expect(options.tension).toBe(0.345); - // dataset-level option overrides chart-level default - expect(options.backgroundColor).toBe('#dad'); - }); - }); - - it('should obey the chart-level dataset options', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 0], - label: 'dataset1' - }], - labels: ['label1', 'label2'] - }, - options: { - datasets: { - line: { - spanGaps: true, - tension: 0.231, - backgroundColor: '#add', - borderWidth: '#daa', - borderColor: '#dad', - borderCapStyle: 'round', - borderDash: [0], - borderDashOffset: 0.871, - borderJoinStyle: 'miter', - fill: 'start', - cubicInterpolationMode: 'monotone' - } - } - } - }); - - var options = chart.getDatasetMeta(0).dataset.options; - - expect(options.spanGaps).toBe(true); - expect(options.tension).toBe(0.231); - expect(options.backgroundColor).toBe('#add'); - expect(options.borderWidth).toBe('#daa'); - expect(options.borderColor).toBe('#dad'); - expect(options.borderCapStyle).toBe('round'); - expect(options.borderDash).toEqual([0]); - expect(options.borderDashOffset).toBe(0.871); - expect(options.borderJoinStyle).toBe('miter'); - expect(options.fill).toBe('start'); - expect(options.cubicInterpolationMode).toBe('monotone'); - }); - - it('should obey the dataset options', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 0], - label: 'dataset1', - spanGaps: true, - tension: 0.231, - backgroundColor: '#add', - borderWidth: '#daa', - borderColor: '#dad', - borderCapStyle: 'round', - borderDash: [0], - borderDashOffset: 0.871, - borderJoinStyle: 'miter', - fill: 'start', - cubicInterpolationMode: 'monotone' - }], - labels: ['label1', 'label2'] - } - }); - - var options = chart.getDatasetMeta(0).dataset.options; - - expect(options.spanGaps).toBe(true); - expect(options.tension).toBe(0.231); - expect(options.backgroundColor).toBe('#add'); - expect(options.borderWidth).toBe('#daa'); - expect(options.borderColor).toBe('#dad'); - expect(options.borderCapStyle).toBe('round'); - expect(options.borderDash).toEqual([0]); - expect(options.borderDashOffset).toBe(0.871); - expect(options.borderJoinStyle).toBe('miter'); - expect(options.fill).toBe('start'); - expect(options.cubicInterpolationMode).toBe('monotone'); - }); - - it('should handle number of data point changes in update', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1', - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - - chart.data.datasets[0].data = [1, 2]; // remove 2 items - chart.update(); - expect(meta.data.length).toBe(2); - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - - chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items - chart.update(); - expect(meta.data.length).toBe(5); - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true); - }); - - describe('Interactions', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['label1', 'label2', 'label3', 'label4'], - datasets: [{ - data: [10, 15, 0, -4] - }] - }, - options: { - scales: { - x: { - offset: true - } - }, - elements: { - point: { - backgroundColor: 'rgb(100, 150, 200)', - borderColor: 'rgb(50, 100, 150)', - borderWidth: 2, - radius: 3 - } - } - } - }); - }); - - it ('should handle default hover styles', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('#3187DD'); - expect(point.options.borderColor).toBe('#175A9D'); - expect(point.options.borderWidth).toBe(1); - expect(point.options.radius).toBe(4); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.data.datasets[0], { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via element options', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.options.elements.point, { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle dataset hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - var dataset = chart.getDatasetMeta(0).dataset; - - Chart.helpers.merge(chart.data.datasets[0], { - backgroundColor: '#AAA', - borderColor: '#BBB', - borderWidth: 6, - hoverBackgroundColor: '#000', - hoverBorderColor: '#111', - hoverBorderWidth: 12 - }); - - chart.options.hover = {mode: 'dataset'}; - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(dataset.options.backgroundColor).toBe('#000'); - expect(dataset.options.borderColor).toBe('#111'); - expect(dataset.options.borderWidth).toBe(12); - - afterEvent(chart, 'mouseout', function() { - expect(dataset.options.backgroundColor).toBe('#AAA'); - expect(dataset.options.borderColor).toBe('#BBB'); - expect(dataset.options.borderWidth).toBe(6); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); - - it('should allow 0 as a point border width', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1', - pointBorderWidth: 0 - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - var point = meta.data[0]; - - expect(point.options.borderWidth).toBe(0); - }); - - it('should allow an array as the point border width setting', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset1', - pointBorderWidth: [1, 2, 3, 4] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data[0].options.borderWidth).toBe(1); - expect(meta.data[1].options.borderWidth).toBe(2); - expect(meta.data[2].options.borderWidth).toBe(3); - expect(meta.data[3].options.borderWidth).toBe(4); - }); - - it('should render a million points', function() { - var data = []; - for (let x = 0; x < 1e6; x++) { - data.push({x, y: Math.sin(x / 10000)}); - } - function createChart() { - window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data, - borderWidth: 1, - radius: 0 - }], - }, - options: { - scales: { - x: {type: 'linear'}, - y: {type: 'linear'} - } - } - }); - } - expect(createChart).not.toThrow(); - }); + 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', + data: { + datasets: [{ + data: [] + }], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('line'); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(0); + expect(meta.data).toEqual([]); + + meta.controller.updateIndex(1); + expect(meta.controller.index).toBe(1); + }); + + it('Should use the first scale IDs if the dataset does not specify them', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }], + labels: [] + }, + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.xAxisID).toBe('x'); + expect(meta.yAxisID).toBe('y'); + }); + + it('Should create line elements and point elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); // 4 points created + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // 1 line element + }); + + it('should draw all elements', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true + } + }); + + var meta = chart.getDatasetMeta(0); + spyOn(meta.dataset, 'updateControlPoints'); + spyOn(meta.dataset, 'draw'); + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.dataset.updateControlPoints.calls.count()).toBeGreaterThanOrEqual(1); + expect(meta.dataset.draw.calls.count()).toBe(1); + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it('should update elements when modifying data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset', + xAxisID: 'x', + yAxisID: 'y' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + plugins: { + legend: false, + title: false + }, + elements: { + point: { + backgroundColor: 'red', + borderColor: 'blue', + } + }, + scales: { + x: { + display: false + }, + y: { + display: false + } + } + }, + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); + + chart.data.datasets[0].data = [1, 2]; // remove 2 items + chart.data.datasets[0].borderWidth = 1; + chart.update(); + + expect(meta.data.length).toBe(2); + expect(meta._parsed.length).toBe(2); + + [ + {x: 0, y: 512}, + {x: 171, y: 0} + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'red', + borderColor: 'blue', + })); + }); + + chart.data.datasets[0].data = [1, 2, 3]; // add 1 items + chart.update(); + + expect(meta.data.length).toBe(3); // should add a new meta data item + }); + + it('should correctly calculate x scale for label and point', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['One'], + datasets: [{ + data: [1], + }] + }, + options: { + plugins: { + legend: false, + title: false + }, + hover: { + mode: 'nearest', + intersect: true + }, + scales: { + x: { + display: false, + }, + y: { + display: false, + beginAtZero: true + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + // 1 point + var point = meta.data[0]; + expect(point.x).toBeCloseToPixel(0); + + // 2 points + chart.data.labels = ['One', 'Two']; + chart.data.datasets[0].data = [1, 2]; + chart.update(); + + var points = meta.data; + + expect(points[0].x).toBeCloseToPixel(0); + expect(points[1].x).toBeCloseToPixel(512); + + // 3 points + chart.data.labels = ['One', 'Two', 'Three']; + chart.data.datasets[0].data = [1, 2, 3]; + chart.update(); + + points = meta.data; + + expect(points[0].x).toBeCloseToPixel(0); + expect(points[1].x).toBeCloseToPixel(256); + expect(points[2].x).toBeCloseToPixel(512); + + // 4 points + chart.data.labels = ['One', 'Two', 'Three', 'Four']; + chart.data.datasets[0].data = [1, 2, 3, 4]; + chart.update(); + + points = meta.data; + + expect(points[0].x).toBeCloseToPixel(0); + expect(points[1].x).toBeCloseToPixel(171); + expect(points[2].x).toBeCloseToPixel(340); + expect(points[3].x).toBeCloseToPixel(512); + }); + + it('should update elements when the y scale is stacked', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, -10, 10, -10], + label: 'dataset1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + display: false, + }, + y: { + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {x: 0, y: 146}, + {x: 171, y: 439}, + {x: 341, y: 146}, + {x: 512, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {x: 0, y: 0}, + {x: 171, y: 73}, + {x: 341, y: 146}, + {x: 512, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + + }); + + it('should update elements when the y scale is stacked with multiple axes', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, -10, 10, -10], + label: 'dataset1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2' + }, { + data: [10, 10, -10, -10], + label: 'dataset3', + yAxisID: 'y2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false, + }, + scales: { + x: { + display: false, + }, + y: { + display: false, + stacked: true + }, + y2: { + type: 'linear', + position: 'right', + display: false + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {x: 0, y: 146}, + {x: 171, y: 439}, + {x: 341, y: 146}, + {x: 512, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {x: 0, y: 0}, + {x: 171, y: 73}, + {x: 341, y: 146}, + {x: 512, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + + }); + + it('should update elements when the y scale is stacked and datasets is scatter data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [{ + x: 0, + y: 10 + }, { + x: 1, + y: -10 + }, { + x: 2, + y: 10 + }, { + x: 3, + y: -10 + }], + label: 'dataset1' + }, { + data: [{ + x: 0, + y: 10 + }, { + x: 1, + y: 15 + }, { + x: 2, + y: 0 + }, { + x: 3, + y: -4 + }], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + display: false, + }, + y: { + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {x: 0, y: 146}, + {x: 171, y: 439}, + {x: 341, y: 146}, + {x: 512, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {x: 0, y: 0}, + {x: 171, y: 73}, + {x: 341, y: 146}, + {x: 512, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + + }); + + it('should update elements when the y scale is stacked and data is strings', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: ['10', '-10', '10', '-10'], + label: 'dataset1' + }, { + data: ['10', '15', '0', '-4'], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + plugins: { + legend: false, + title: false + }, + scales: { + x: { + display: false, + }, + y: { + display: false, + stacked: true + } + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {x: 0, y: 146}, + {x: 171, y: 439}, + {x: 341, y: 146}, + {x: 512, y: 439} + ].forEach(function(values, i) { + expect(meta0.data[i].x).toBeCloseToPixel(values.x); + expect(meta0.data[i].y).toBeCloseToPixel(values.y); + }); + + var meta1 = chart.getDatasetMeta(1); + + [ + {x: 0, y: 0}, + {x: 171, y: 73}, + {x: 341, y: 146}, + {x: 512, y: 497} + ].forEach(function(values, i) { + expect(meta1.data[i].x).toBeCloseToPixel(values.x); + expect(meta1.data[i].y).toBeCloseToPixel(values.y); + }); + + }); + + it('should fall back to the line styles for points', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1', + + // line styles + backgroundColor: 'rgb(98, 98, 98)', + borderColor: 'rgb(8, 8, 8)', + borderWidth: 0.55, + }], + labels: ['label1', 'label2'] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.dataset.options.backgroundColor).toBe('rgb(98, 98, 98)'); + expect(meta.dataset.options.borderColor).toBe('rgb(8, 8, 8)'); + expect(meta.dataset.options.borderWidth).toBe(0.55); + }); + + describe('dataset global defaults', function() { + beforeEach(function() { + this._defaults = Chart.helpers.clone(Chart.defaults.controllers.line.datasets); + }); + + afterEach(function() { + Chart.defaults.controllers.line.datasets = this._defaults; + delete this._defaults; + }); + + it('should utilize the dataset global default options', function() { + Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {}; + + Chart.helpers.merge(Chart.defaults.controllers.line.datasets, { + spanGaps: true, + tension: 0.231, + backgroundColor: '#add', + borderWidth: '#daa', + borderColor: '#dad', + borderCapStyle: 'round', + borderDash: [0], + borderDashOffset: 0.871, + borderJoinStyle: 'miter', + fill: 'start', + cubicInterpolationMode: 'monotone' + }); + + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1' + }], + labels: ['label1', 'label2'] + } + }); + + var options = chart.getDatasetMeta(0).dataset.options; + + expect(options.spanGaps).toBe(true); + expect(options.tension).toBe(0.231); + expect(options.backgroundColor).toBe('#add'); + expect(options.borderWidth).toBe('#daa'); + expect(options.borderColor).toBe('#dad'); + expect(options.borderCapStyle).toBe('round'); + expect(options.borderDash).toEqual([0]); + expect(options.borderDashOffset).toBe(0.871); + expect(options.borderJoinStyle).toBe('miter'); + expect(options.fill).toBe('start'); + expect(options.cubicInterpolationMode).toBe('monotone'); + }); + + it('should be overriden by user-supplied values', function() { + Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {}; + + Chart.helpers.merge(Chart.defaults.controllers.line.datasets, { + spanGaps: true, + tension: 0.231 + }); + + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1', + spanGaps: true, + backgroundColor: '#dad' + }], + labels: ['label1', 'label2'] + }, + options: { + datasets: { + line: { + tension: 0.345, + backgroundColor: '#add' + } + } + } + }); + + var options = chart.getDatasetMeta(0).dataset.options; + + // dataset-level option overrides global default + expect(options.spanGaps).toBe(true); + // chart-level default overrides global default + expect(options.tension).toBe(0.345); + // dataset-level option overrides chart-level default + expect(options.backgroundColor).toBe('#dad'); + }); + }); + + it('should obey the chart-level dataset options', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1' + }], + labels: ['label1', 'label2'] + }, + options: { + datasets: { + line: { + spanGaps: true, + tension: 0.231, + backgroundColor: '#add', + borderWidth: '#daa', + borderColor: '#dad', + borderCapStyle: 'round', + borderDash: [0], + borderDashOffset: 0.871, + borderJoinStyle: 'miter', + fill: 'start', + cubicInterpolationMode: 'monotone' + } + } + } + }); + + var options = chart.getDatasetMeta(0).dataset.options; + + expect(options.spanGaps).toBe(true); + expect(options.tension).toBe(0.231); + expect(options.backgroundColor).toBe('#add'); + expect(options.borderWidth).toBe('#daa'); + expect(options.borderColor).toBe('#dad'); + expect(options.borderCapStyle).toBe('round'); + expect(options.borderDash).toEqual([0]); + expect(options.borderDashOffset).toBe(0.871); + expect(options.borderJoinStyle).toBe('miter'); + expect(options.fill).toBe('start'); + expect(options.cubicInterpolationMode).toBe('monotone'); + }); + + it('should obey the dataset options', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1', + spanGaps: true, + tension: 0.231, + backgroundColor: '#add', + borderWidth: '#daa', + borderColor: '#dad', + borderCapStyle: 'round', + borderDash: [0], + borderDashOffset: 0.871, + borderJoinStyle: 'miter', + fill: 'start', + cubicInterpolationMode: 'monotone' + }], + labels: ['label1', 'label2'] + } + }); + + var options = chart.getDatasetMeta(0).dataset.options; + + expect(options.spanGaps).toBe(true); + expect(options.tension).toBe(0.231); + expect(options.backgroundColor).toBe('#add'); + expect(options.borderWidth).toBe('#daa'); + expect(options.borderColor).toBe('#dad'); + expect(options.borderCapStyle).toBe('round'); + expect(options.borderDash).toEqual([0]); + expect(options.borderDashOffset).toBe(0.871); + expect(options.borderJoinStyle).toBe('miter'); + expect(options.fill).toBe('start'); + expect(options.cubicInterpolationMode).toBe('monotone'); + }); + + it('should handle number of data point changes in update', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + + chart.data.datasets[0].data = [1, 2]; // remove 2 items + chart.update(); + expect(meta.data.length).toBe(2); + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + + chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items + chart.update(); + expect(meta.data.length).toBe(5); + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true); + }); + + describe('Interactions', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['label1', 'label2', 'label3', 'label4'], + datasets: [{ + data: [10, 15, 0, -4] + }] + }, + options: { + scales: { + x: { + offset: true + } + }, + elements: { + point: { + backgroundColor: 'rgb(100, 150, 200)', + borderColor: 'rgb(50, 100, 150)', + borderWidth: 2, + radius: 3 + } + } + } + }); + }); + + it ('should handle default hover styles', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('#3187DD'); + expect(point.options.borderColor).toBe('#175A9D'); + expect(point.options.borderWidth).toBe(1); + expect(point.options.radius).toBe(4); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.data.datasets[0], { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via element options', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.options.elements.point, { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle dataset hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + var dataset = chart.getDatasetMeta(0).dataset; + + Chart.helpers.merge(chart.data.datasets[0], { + backgroundColor: '#AAA', + borderColor: '#BBB', + borderWidth: 6, + hoverBackgroundColor: '#000', + hoverBorderColor: '#111', + hoverBorderWidth: 12 + }); + + chart.options.hover = {mode: 'dataset'}; + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(dataset.options.backgroundColor).toBe('#000'); + expect(dataset.options.borderColor).toBe('#111'); + expect(dataset.options.borderWidth).toBe(12); + + afterEvent(chart, 'mouseout', function() { + expect(dataset.options.backgroundColor).toBe('#AAA'); + expect(dataset.options.borderColor).toBe('#BBB'); + expect(dataset.options.borderWidth).toBe(6); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); + + it('should allow 0 as a point border width', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + pointBorderWidth: 0 + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + var point = meta.data[0]; + + expect(point.options.borderWidth).toBe(0); + }); + + it('should allow an array as the point border width setting', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + pointBorderWidth: [1, 2, 3, 4] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data[0].options.borderWidth).toBe(1); + expect(meta.data[1].options.borderWidth).toBe(2); + expect(meta.data[2].options.borderWidth).toBe(3); + expect(meta.data[3].options.borderWidth).toBe(4); + }); + + it('should render a million points', function() { + var data = []; + for (let x = 0; x < 1e6; x++) { + data.push({x, y: Math.sin(x / 10000)}); + } + function createChart() { + window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data, + borderWidth: 1, + radius: 0 + }], + }, + options: { + scales: { + x: {type: 'linear'}, + y: {type: 'linear'} + } + } + }); + } + expect(createChart).not.toThrow(); + }); }); diff --git a/test/specs/controller.polarArea.tests.js b/test/specs/controller.polarArea.tests.js index 5afa179125c..b5895a1b335 100644 --- a/test/specs/controller.polarArea.tests.js +++ b/test/specs/controller.polarArea.tests.js @@ -1,346 +1,346 @@ 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', - data: { - datasets: [ - {data: []}, - {data: []} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.type).toEqual('polarArea'); - expect(meta.data).toEqual([]); - expect(meta.hidden).toBe(null); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(1); - - meta.controller.updateIndex(0); - expect(meta.controller.index).toBe(0); - }); - - it('should create arc elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'polarArea', - data: { - datasets: [ - {data: []}, - {data: [10, 15, 0, -4]} - ], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(1); - expect(meta.data.length).toBe(4); // 4 arcs created - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); - }); - - it('should draw all elements', function() { - var chart = window.acquireChart({ - type: 'polarArea', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it('should update elements when modifying data', function() { - var chart = window.acquireChart({ - type: 'polarArea', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - plugins: { - legend: false, - title: false - }, - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 255, 0)', - borderWidth: 1.2 - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); - - [ - {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].x).toBeCloseToPixel(256); - expect(meta.data[i].y).toBeCloseToPixel(259); - expect(meta.data[i].innerRadius).toBeCloseToPixel(0); - expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o); - expect(meta.data[i].startAngle).toBe(expected.s); - expect(meta.data[i].endAngle).toBe(expected.e); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 255, 0)', - borderWidth: 1.2 - })); - }); - - // arc styles - chart.data.datasets[0].backgroundColor = 'rgb(128, 129, 130)'; - chart.data.datasets[0].borderColor = 'rgb(56, 57, 58)'; - chart.data.datasets[0].borderWidth = 1.123; - - chart.update(); - - for (var i = 0; i < 4; ++i) { - expect(meta.data[i].options.backgroundColor).toBe('rgb(128, 129, 130)'); - expect(meta.data[i].options.borderColor).toBe('rgb(56, 57, 58)'); - expect(meta.data[i].options.borderWidth).toBe(1.123); - } - - chart.update(); - - expect(meta.data[0].x).toBeCloseToPixel(256); - expect(meta.data[0].y).toBeCloseToPixel(259); - expect(meta.data[0].innerRadius).toBeCloseToPixel(0); - expect(meta.data[0].outerRadius).toBeCloseToPixel(177); - }); - - it('should update elements with start angle from options', function() { - var chart = window.acquireChart({ - type: 'polarArea', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - plugins: { - legend: false, - title: false, - }, - startAngle: 90, // default is 0 - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 255, 0)', - borderWidth: 1.2 - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); - - [ - {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].x).toBeCloseToPixel(256); - expect(meta.data[i].y).toBeCloseToPixel(259); - expect(meta.data[i].innerRadius).toBeCloseToPixel(0); - expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o); - expect(meta.data[i].startAngle).toBe(expected.s); - expect(meta.data[i].endAngle).toBe(expected.e); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 255, 0)', - borderWidth: 1.2 - })); - }); - }); - - it('should handle number of data point changes in update', function() { - var chart = window.acquireChart({ - type: 'polarArea', - data: { - datasets: [{ - data: [10, 15, 0, -4], - label: 'dataset2' - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - elements: { - arc: { - backgroundColor: 'rgb(255, 0, 0)', - borderColor: 'rgb(0, 255, 0)', - borderWidth: 1.2 - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.data.length).toBe(4); - - // remove 2 items - chart.data.labels = ['label1', 'label2']; - chart.data.datasets[0].data = [1, 2]; - chart.update(); - - expect(meta.data.length).toBe(2); - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - - // add 3 items - chart.data.labels = ['label1', 'label2', 'label3', 'label4', 'label5']; - chart.data.datasets[0].data = [1, 2, 3, 4, 5]; - chart.update(); - - expect(meta.data.length).toBe(5); - expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); - expect(meta.data[4] instanceof Chart.elements.ArcElement).toBe(true); - }); - - describe('Interactions', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'polarArea', - data: { - labels: ['label1', 'label2', 'label3', 'label4'], - datasets: [{ - data: [10, 15, 0, 4] - }] - }, - options: { - cutoutPercentage: 0, - elements: { - arc: { - backgroundColor: 'rgb(100, 150, 200)', - borderColor: 'rgb(50, 100, 150)', - borderWidth: 2, - } - } - } - }); - }); - - it ('should handle default hover styles', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('#3187DD'); - expect(arc.options.borderColor).toBe('#175A9D'); - expect(arc.options.borderWidth).toBe(2); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - - it ('should handle hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.data.datasets[0], { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(arc.options.borderWidth).toBe(8.4); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - - it ('should handle hover styles defined via element options', function(done) { - var chart = this.chart; - var arc = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.options.elements.arc, { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(arc.options.borderWidth).toBe(8.4); - - afterEvent(chart, 'mouseout', function() { - expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(arc.options.borderWidth).toBe(2); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', arc); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', arc); - }); - }); + 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', + data: { + datasets: [ + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.type).toEqual('polarArea'); + expect(meta.data).toEqual([]); + expect(meta.hidden).toBe(null); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(1); + + meta.controller.updateIndex(0); + expect(meta.controller.index).toBe(0); + }); + + it('should create arc elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [ + {data: []}, + {data: [10, 15, 0, -4]} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.data.length).toBe(4); // 4 arcs created + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); + }); + + it('should draw all elements', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it('should update elements when modifying data', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + plugins: { + legend: false, + title: false + }, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 255, 0)', + borderWidth: 1.2 + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); + + [ + {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].x).toBeCloseToPixel(256); + expect(meta.data[i].y).toBeCloseToPixel(259); + expect(meta.data[i].innerRadius).toBeCloseToPixel(0); + expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o); + expect(meta.data[i].startAngle).toBe(expected.s); + expect(meta.data[i].endAngle).toBe(expected.e); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 255, 0)', + borderWidth: 1.2 + })); + }); + + // arc styles + chart.data.datasets[0].backgroundColor = 'rgb(128, 129, 130)'; + chart.data.datasets[0].borderColor = 'rgb(56, 57, 58)'; + chart.data.datasets[0].borderWidth = 1.123; + + chart.update(); + + for (var i = 0; i < 4; ++i) { + expect(meta.data[i].options.backgroundColor).toBe('rgb(128, 129, 130)'); + expect(meta.data[i].options.borderColor).toBe('rgb(56, 57, 58)'); + expect(meta.data[i].options.borderWidth).toBe(1.123); + } + + chart.update(); + + expect(meta.data[0].x).toBeCloseToPixel(256); + expect(meta.data[0].y).toBeCloseToPixel(259); + expect(meta.data[0].innerRadius).toBeCloseToPixel(0); + expect(meta.data[0].outerRadius).toBeCloseToPixel(177); + }); + + it('should update elements with start angle from options', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + plugins: { + legend: false, + title: false, + }, + startAngle: 90, // default is 0 + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 255, 0)', + borderWidth: 1.2 + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); + + [ + {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].x).toBeCloseToPixel(256); + expect(meta.data[i].y).toBeCloseToPixel(259); + expect(meta.data[i].innerRadius).toBeCloseToPixel(0); + expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o); + expect(meta.data[i].startAngle).toBe(expected.s); + expect(meta.data[i].endAngle).toBe(expected.e); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 255, 0)', + borderWidth: 1.2 + })); + }); + }); + + it('should handle number of data point changes in update', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + elements: { + arc: { + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(0, 255, 0)', + borderWidth: 1.2 + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data.length).toBe(4); + + // remove 2 items + chart.data.labels = ['label1', 'label2']; + chart.data.datasets[0].data = [1, 2]; + chart.update(); + + expect(meta.data.length).toBe(2); + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + + // add 3 items + chart.data.labels = ['label1', 'label2', 'label3', 'label4', 'label5']; + chart.data.datasets[0].data = [1, 2, 3, 4, 5]; + chart.update(); + + expect(meta.data.length).toBe(5); + expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true); + expect(meta.data[4] instanceof Chart.elements.ArcElement).toBe(true); + }); + + describe('Interactions', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: ['label1', 'label2', 'label3', 'label4'], + datasets: [{ + data: [10, 15, 0, 4] + }] + }, + options: { + cutoutPercentage: 0, + elements: { + arc: { + backgroundColor: 'rgb(100, 150, 200)', + borderColor: 'rgb(50, 100, 150)', + borderWidth: 2, + } + } + } + }); + }); + + it ('should handle default hover styles', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('#3187DD'); + expect(arc.options.borderColor).toBe('#175A9D'); + expect(arc.options.borderWidth).toBe(2); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + + it ('should handle hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.data.datasets[0], { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(arc.options.borderWidth).toBe(8.4); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + + it ('should handle hover styles defined via element options', function(done) { + var chart = this.chart; + var arc = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.options.elements.arc, { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(arc.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(arc.options.borderWidth).toBe(8.4); + + afterEvent(chart, 'mouseout', function() { + expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(arc.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(arc.options.borderWidth).toBe(2); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', arc); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', arc); + }); + }); }); diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index 9a9581fd567..c2fc1f2cdf1 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -1,403 +1,403 @@ 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', - data: { - datasets: [{ - data: [] - }], - labels: [] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('radar'); - expect(meta.controller).not.toBe(undefined); - expect(meta.controller.index).toBe(0); - expect(meta.data).toEqual([]); - - meta.controller.updateIndex(1); - expect(meta.controller.index).toBe(1); - }); - - it('Should create arc elements for each data item during initialization', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // line element - expect(meta.data.length).toBe(4); // 4 points created - expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); - expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); - }); - - it('should draw all elements', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - - spyOn(meta.dataset, 'draw'); - spyOn(meta.data[0], 'draw'); - spyOn(meta.data[1], 'draw'); - spyOn(meta.data[2], 'draw'); - spyOn(meta.data[3], 'draw'); - - chart.update(); - - expect(meta.dataset.draw.calls.count()).toBe(1); - expect(meta.data[0].draw.calls.count()).toBe(1); - expect(meta.data[1].draw.calls.count()).toBe(1); - expect(meta.data[2].draw.calls.count()).toBe(1); - expect(meta.data[3].draw.calls.count()).toBe(1); - }); - - it('should update elements', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - showLine: true, - plugins: { - legend: false, - title: false, - }, - elements: { - line: { - backgroundColor: 'rgb(255, 0, 0)', - borderCapStyle: 'round', - borderColor: 'rgb(0, 255, 0)', - borderDash: [], - borderDashOffset: 0.1, - borderJoinStyle: 'bevel', - borderWidth: 1.2, - fill: true, - tension: 0.1, - }, - point: { - backgroundColor: Chart.defaults.backgroundColor, - borderWidth: 1, - borderColor: Chart.defaults.borderColor, - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1, - radius: 3, - pointStyle: 'circle' - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - - meta.controller.reset(); // reset first - - // Line element - expect(meta.dataset.options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(255, 0, 0)', - borderCapStyle: 'round', - borderColor: 'rgb(0, 255, 0)', - borderDash: [], - borderDashOffset: 0.1, - borderJoinStyle: 'bevel', - borderWidth: 1.2, - fill: true, - tension: 0.1, - })); - - [ - {x: 256, y: 260}, - {x: 256, y: 260}, - {x: 256, y: 260}, - {x: 256, y: 260}, - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: Chart.defaults.backgroundColor, - borderWidth: 1, - borderColor: Chart.defaults.borderColor, - hitRadius: 1, - radius: 3, - pointStyle: 'circle', - })); - }); - - // Now update controller and ensure proper updates - meta.controller._update(); - - [ - {x: 256, y: 120, cppx: 246, cppy: 120, cpnx: 272, cpny: 120}, - {x: 464, y: 260, cppx: 464, cppy: 252, cpnx: 464, cpny: 266}, - {x: 256, y: 260, cppx: 277, cppy: 260, cpnx: 250, cpny: 260}, - {x: 200, y: 260, cppx: 200, cppy: 264, cpnx: 200, cpny: 250}, - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].controlPointPreviousX).toBeCloseToPixel(expected.cppx); - expect(meta.data[i].controlPointPreviousY).toBeCloseToPixel(expected.cppy); - expect(meta.data[i].controlPointNextX).toBeCloseToPixel(expected.cpnx); - expect(meta.data[i].controlPointNextY).toBeCloseToPixel(expected.cpny); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: Chart.defaults.backgroundColor, - borderWidth: 1, - borderColor: Chart.defaults.borderColor, - hitRadius: 1, - radius: 3, - pointStyle: 'circle', - })); - }); - - // Use dataset level styles for lines & points - chart.data.datasets[0].tension = 0; - chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)'; - chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)'; - chart.data.datasets[0].borderWidth = 0.55; - chart.data.datasets[0].borderCapStyle = 'butt'; - chart.data.datasets[0].borderDash = [2, 3]; - chart.data.datasets[0].borderDashOffset = 7; - chart.data.datasets[0].borderJoinStyle = 'miter'; - chart.data.datasets[0].fill = false; - - // point styles - chart.data.datasets[0].pointRadius = 22; - chart.data.datasets[0].hitRadius = 3.3; - chart.data.datasets[0].pointBackgroundColor = 'rgb(128, 129, 130)'; - chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)'; - chart.data.datasets[0].pointBorderWidth = 1.123; - - meta.controller._update(); - - expect(meta.dataset.options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(98, 98, 98)', - borderCapStyle: 'butt', - borderColor: 'rgb(8, 8, 8)', - borderDash: [2, 3], - borderDashOffset: 7, - borderJoinStyle: 'miter', - borderWidth: 0.55, - fill: false, - tension: 0, - })); - - // Since tension is now 0, we don't care about the control points - [ - {x: 256, y: 120}, - {x: 464, y: 260}, - {x: 256, y: 260}, - {x: 200, y: 260}, - ].forEach(function(expected, i) { - expect(meta.data[i].x).toBeCloseToPixel(expected.x); - expect(meta.data[i].y).toBeCloseToPixel(expected.y); - expect(meta.data[i].options).toEqual(jasmine.objectContaining({ - backgroundColor: 'rgb(128, 129, 130)', - borderWidth: 1.123, - borderColor: 'rgb(56, 57, 58)', - hitRadius: 3.3, - radius: 22, - pointStyle: 'circle' - })); - }); - }); - - describe('Interactions', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'radar', - data: { - labels: ['label1', 'label2', 'label3', 'label4'], - datasets: [{ - data: [10, 15, 0, 4] - }] - }, - options: { - elements: { - point: { - backgroundColor: 'rgb(100, 150, 200)', - borderColor: 'rgb(50, 100, 150)', - borderWidth: 2, - radius: 3 - } - } - } - }); - }); - - it ('should handle default hover styles', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('#3187DD'); - expect(point.options.borderColor).toBe('#175A9D'); - expect(point.options.borderWidth).toBe(1); - expect(point.options.radius).toBe(4); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via dataset properties', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.data.datasets[0], { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should handle hover styles defined via element options', function(done) { - var chart = this.chart; - var point = chart.getDatasetMeta(0).data[0]; - - Chart.helpers.merge(chart.options.elements.point, { - hoverBackgroundColor: 'rgb(200, 100, 150)', - hoverBorderColor: 'rgb(150, 50, 100)', - hoverBorderWidth: 8.4, - hoverRadius: 4.2 - }); - - chart.update(); - - afterEvent(chart, 'mousemove', function() { - expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); - expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); - expect(point.options.borderWidth).toBe(8.4); - expect(point.options.radius).toBe(4.2); - - afterEvent(chart, 'mouseout', function() { - expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); - expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); - expect(point.options.borderWidth).toBe(2); - expect(point.options.radius).toBe(3); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mouseout', point); - - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); - - it('should allow pointBorderWidth to be set to 0', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4], - pointBorderWidth: 0 - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta = chart.getDatasetMeta(0); - var point = meta.data[0]; - expect(point.options.borderWidth).toBe(0); - }); - - it('should use the pointRadius setting over the radius setting', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4], - pointRadius: 10, - radius: 15, - }, { - data: [20, 20, 20, 20], - radius: 20 - }], - labels: ['label1', 'label2', 'label3', 'label4'] - } - }); - - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - expect(meta0.data[0].options.radius).toBe(10); - expect(meta1.data[0].options.radius).toBe(20); - }); - - it('should return id for value scale', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 15, 0, 4], - pointBorderWidth: 0 - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - scales: { - test: { - axis: 'r' - } - } - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.vScale.id).toBe('test'); - }); + 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', + data: { + datasets: [{ + data: [] + }], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('radar'); + expect(meta.controller).not.toBe(undefined); + expect(meta.controller.index).toBe(0); + expect(meta.data).toEqual([]); + + meta.controller.updateIndex(1); + expect(meta.controller.index).toBe(1); + }); + + it('Should create arc elements for each data item during initialization', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // line element + expect(meta.data.length).toBe(4); // 4 points created + expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true); + expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true); + }); + + it('should draw all elements', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + + spyOn(meta.dataset, 'draw'); + spyOn(meta.data[0], 'draw'); + spyOn(meta.data[1], 'draw'); + spyOn(meta.data[2], 'draw'); + spyOn(meta.data[3], 'draw'); + + chart.update(); + + expect(meta.dataset.draw.calls.count()).toBe(1); + expect(meta.data[0].draw.calls.count()).toBe(1); + expect(meta.data[1].draw.calls.count()).toBe(1); + expect(meta.data[2].draw.calls.count()).toBe(1); + expect(meta.data[3].draw.calls.count()).toBe(1); + }); + + it('should update elements', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + showLine: true, + plugins: { + legend: false, + title: false, + }, + elements: { + line: { + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + tension: 0.1, + }, + point: { + backgroundColor: Chart.defaults.backgroundColor, + borderWidth: 1, + borderColor: Chart.defaults.borderColor, + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + radius: 3, + pointStyle: 'circle' + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + + meta.controller.reset(); // reset first + + // Line element + expect(meta.dataset.options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(255, 0, 0)', + borderCapStyle: 'round', + borderColor: 'rgb(0, 255, 0)', + borderDash: [], + borderDashOffset: 0.1, + borderJoinStyle: 'bevel', + borderWidth: 1.2, + fill: true, + tension: 0.1, + })); + + [ + {x: 256, y: 260}, + {x: 256, y: 260}, + {x: 256, y: 260}, + {x: 256, y: 260}, + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: Chart.defaults.backgroundColor, + borderWidth: 1, + borderColor: Chart.defaults.borderColor, + hitRadius: 1, + radius: 3, + pointStyle: 'circle', + })); + }); + + // Now update controller and ensure proper updates + meta.controller._update(); + + [ + {x: 256, y: 120, cppx: 246, cppy: 120, cpnx: 272, cpny: 120}, + {x: 464, y: 260, cppx: 464, cppy: 252, cpnx: 464, cpny: 266}, + {x: 256, y: 260, cppx: 277, cppy: 260, cpnx: 250, cpny: 260}, + {x: 200, y: 260, cppx: 200, cppy: 264, cpnx: 200, cpny: 250}, + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].controlPointPreviousX).toBeCloseToPixel(expected.cppx); + expect(meta.data[i].controlPointPreviousY).toBeCloseToPixel(expected.cppy); + expect(meta.data[i].controlPointNextX).toBeCloseToPixel(expected.cpnx); + expect(meta.data[i].controlPointNextY).toBeCloseToPixel(expected.cpny); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: Chart.defaults.backgroundColor, + borderWidth: 1, + borderColor: Chart.defaults.borderColor, + hitRadius: 1, + radius: 3, + pointStyle: 'circle', + })); + }); + + // Use dataset level styles for lines & points + chart.data.datasets[0].tension = 0; + chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)'; + chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)'; + chart.data.datasets[0].borderWidth = 0.55; + chart.data.datasets[0].borderCapStyle = 'butt'; + chart.data.datasets[0].borderDash = [2, 3]; + chart.data.datasets[0].borderDashOffset = 7; + chart.data.datasets[0].borderJoinStyle = 'miter'; + chart.data.datasets[0].fill = false; + + // point styles + chart.data.datasets[0].pointRadius = 22; + chart.data.datasets[0].hitRadius = 3.3; + chart.data.datasets[0].pointBackgroundColor = 'rgb(128, 129, 130)'; + chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)'; + chart.data.datasets[0].pointBorderWidth = 1.123; + + meta.controller._update(); + + expect(meta.dataset.options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(98, 98, 98)', + borderCapStyle: 'butt', + borderColor: 'rgb(8, 8, 8)', + borderDash: [2, 3], + borderDashOffset: 7, + borderJoinStyle: 'miter', + borderWidth: 0.55, + fill: false, + tension: 0, + })); + + // Since tension is now 0, we don't care about the control points + [ + {x: 256, y: 120}, + {x: 464, y: 260}, + {x: 256, y: 260}, + {x: 200, y: 260}, + ].forEach(function(expected, i) { + expect(meta.data[i].x).toBeCloseToPixel(expected.x); + expect(meta.data[i].y).toBeCloseToPixel(expected.y); + expect(meta.data[i].options).toEqual(jasmine.objectContaining({ + backgroundColor: 'rgb(128, 129, 130)', + borderWidth: 1.123, + borderColor: 'rgb(56, 57, 58)', + hitRadius: 3.3, + radius: 22, + pointStyle: 'circle' + })); + }); + }); + + describe('Interactions', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'radar', + data: { + labels: ['label1', 'label2', 'label3', 'label4'], + datasets: [{ + data: [10, 15, 0, 4] + }] + }, + options: { + elements: { + point: { + backgroundColor: 'rgb(100, 150, 200)', + borderColor: 'rgb(50, 100, 150)', + borderWidth: 2, + radius: 3 + } + } + } + }); + }); + + it ('should handle default hover styles', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('#3187DD'); + expect(point.options.borderColor).toBe('#175A9D'); + expect(point.options.borderWidth).toBe(1); + expect(point.options.radius).toBe(4); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via dataset properties', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.data.datasets[0], { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should handle hover styles defined via element options', function(done) { + var chart = this.chart; + var point = chart.getDatasetMeta(0).data[0]; + + Chart.helpers.merge(chart.options.elements.point, { + hoverBackgroundColor: 'rgb(200, 100, 150)', + hoverBorderColor: 'rgb(150, 50, 100)', + hoverBorderWidth: 8.4, + hoverRadius: 4.2 + }); + + chart.update(); + + afterEvent(chart, 'mousemove', function() { + expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)'); + expect(point.options.borderColor).toBe('rgb(150, 50, 100)'); + expect(point.options.borderWidth).toBe(8.4); + expect(point.options.radius).toBe(4.2); + + afterEvent(chart, 'mouseout', function() { + expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)'); + expect(point.options.borderColor).toBe('rgb(50, 100, 150)'); + expect(point.options.borderWidth).toBe(2); + expect(point.options.radius).toBe(3); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mouseout', point); + + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); + + it('should allow pointBorderWidth to be set to 0', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4], + pointBorderWidth: 0 + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + var point = meta.data[0]; + expect(point.options.borderWidth).toBe(0); + }); + + it('should use the pointRadius setting over the radius setting', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4], + pointRadius: 10, + radius: 15, + }, { + data: [20, 20, 20, 20], + radius: 20 + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + expect(meta0.data[0].options.radius).toBe(10); + expect(meta1.data[0].options.radius).toBe(20); + }); + + it('should return id for value scale', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 15, 0, 4], + pointBorderWidth: 0 + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + scales: { + test: { + axis: 'r' + } + } + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.vScale.id).toBe('test'); + }); }); diff --git a/test/specs/controller.scatter.tests.js b/test/specs/controller.scatter.tests.js index b181c91c5ee..5f3aa27c3dd 100644 --- a/test/specs/controller.scatter.tests.js +++ b/test/specs/controller.scatter.tests.js @@ -1,74 +1,74 @@ describe('Chart.controllers.scatter', function() { - describe('auto', jasmine.fixture.specs('controller.scatter')); + describe('auto', jasmine.fixture.specs('controller.scatter')); - it('should be registered as dataset controller', function() { - expect(typeof Chart.controllers.scatter).toBe('function'); - }); + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.scatter).toBe('function'); + }); - it('should test default tooltip callbacks', function(done) { - var chart = window.acquireChart({ - type: 'scatter', - data: { - datasets: [{ - data: [{ - x: 10, - y: 15 - }], - label: 'dataset1' - }], - }, - options: {} - }); - var point = chart.getDatasetMeta(0).data[0]; + it('should test default tooltip callbacks', function(done) { + var chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{ + x: 10, + y: 15 + }], + label: 'dataset1' + }], + }, + options: {} + }); + var point = chart.getDatasetMeta(0).data[0]; - afterEvent(chart, 'mousemove', function() { - // Title should be empty - expect(chart.tooltip.title.length).toBe(0); - expect(chart.tooltip.body[0].lines).toEqual(['(10, 15)']); + afterEvent(chart, 'mousemove', function() { + // Title should be empty + expect(chart.tooltip.title.length).toBe(0); + expect(chart.tooltip.body[0].lines).toEqual(['(10, 15)']); - done(); - }); + done(); + }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); - it('should only show a single point in the tooltip on multiple datasets', function(done) { - var chart = window.acquireChart({ - type: 'scatter', - data: { - datasets: [{ - data: [{ - x: 10, - y: 15 - }, - { - x: 12, - y: 10 - }], - label: 'dataset1' - }, - { - data: [{ - x: 20, - y: 10 - }, - { - x: 4, - y: 8 - }], - label: 'dataset2' - }] - }, - options: {} - }); - var point = chart.getDatasetMeta(0).data[1]; + it('should only show a single point in the tooltip on multiple datasets', function(done) { + var chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{ + x: 10, + y: 15 + }, + { + x: 12, + y: 10 + }], + label: 'dataset1' + }, + { + data: [{ + x: 20, + y: 10 + }, + { + x: 4, + y: 8 + }], + label: 'dataset2' + }] + }, + options: {} + }); + var point = chart.getDatasetMeta(0).data[1]; - afterEvent(chart, 'mousemove', function() { - expect(chart.tooltip.body.length).toEqual(1); + afterEvent(chart, 'mousemove', function() { + expect(chart.tooltip.body.length).toEqual(1); - done(); - }); + done(); + }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); }); diff --git a/test/specs/core.animation.tests.js b/test/specs/core.animation.tests.js index ed566f4cbf6..ab4d7e4311c 100644 --- a/test/specs/core.animation.tests.js +++ b/test/specs/core.animation.tests.js @@ -1,86 +1,86 @@ describe('Chart.Animation', function() { - it('should animate boolean', function() { - const target = {prop: false}; - const anim = new Chart.Animation({duration: 1000}, target, 'prop', true); - expect(anim.active()).toBeTrue(); + it('should animate boolean', function() { + const target = {prop: false}; + const anim = new Chart.Animation({duration: 1000}, target, 'prop', true); + expect(anim.active()).toBeTrue(); - anim.tick(anim._start + 500); - expect(anim.active()).toBeTrue(); - expect(target.prop).toBeFalse(); + anim.tick(anim._start + 500); + expect(anim.active()).toBeTrue(); + expect(target.prop).toBeFalse(); - anim.tick(anim._start + 501); - expect(anim.active()).toBeTrue(); - expect(target.prop).toBeTrue(); + anim.tick(anim._start + 501); + expect(anim.active()).toBeTrue(); + expect(target.prop).toBeTrue(); - anim.tick(anim._start - 100); - expect(anim.active()).toBeTrue(); - expect(target.prop).toBeFalse(); + anim.tick(anim._start - 100); + expect(anim.active()).toBeTrue(); + expect(target.prop).toBeFalse(); - anim.tick(anim._start + 1000); - expect(anim.active()).toBeFalse(); - expect(target.prop).toBeTrue(); - }); + anim.tick(anim._start + 1000); + expect(anim.active()).toBeFalse(); + expect(target.prop).toBeTrue(); + }); - describe('color', function() { - it('should fall back to transparent', function() { - const target = {}; - const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'red'); - anim._from = undefined; - anim.tick(anim._start + 500); - expect(target.color).toEqual('#FF000080'); + describe('color', function() { + it('should fall back to transparent', function() { + const target = {}; + const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'red'); + anim._from = undefined; + anim.tick(anim._start + 500); + expect(target.color).toEqual('#FF000080'); - anim._from = 'blue'; - anim._to = undefined; - anim.tick(anim._start + 500); - expect(target.color).toEqual('#0000FF80'); - }); + anim._from = 'blue'; + anim._to = undefined; + anim.tick(anim._start + 500); + expect(target.color).toEqual('#0000FF80'); + }); - it('should not try to mix invalid color', function() { - const target = {color: 'blue'}; - const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'invalid'); - anim.tick(anim._start + 500); - expect(target.color).toEqual('invalid'); - }); - }); + it('should not try to mix invalid color', function() { + const target = {color: 'blue'}; + const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'invalid'); + anim.tick(anim._start + 500); + expect(target.color).toEqual('invalid'); + }); + }); - it('should loop', function() { - const target = {value: 0}; - const anim = new Chart.Animation({duration: 100, loop: true}, target, 'value', 10); - anim.tick(anim._start + 50); - expect(target.value).toEqual(5); - anim.tick(anim._start + 100); - expect(target.value).toEqual(10); - anim.tick(anim._start + 150); - expect(target.value).toEqual(5); - anim.tick(anim._start + 400); - expect(target.value).toEqual(0); - }); + it('should loop', function() { + const target = {value: 0}; + const anim = new Chart.Animation({duration: 100, loop: true}, target, 'value', 10); + anim.tick(anim._start + 50); + expect(target.value).toEqual(5); + anim.tick(anim._start + 100); + expect(target.value).toEqual(10); + anim.tick(anim._start + 150); + expect(target.value).toEqual(5); + anim.tick(anim._start + 400); + expect(target.value).toEqual(0); + }); - it('should update', function() { - const target = {testColor: 'transparent'}; - const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red'); + it('should update', function() { + const target = {testColor: 'transparent'}; + const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red'); - anim.tick(anim._start + 50); - expect(target.testColor).toEqual('#FF000080'); + anim.tick(anim._start + 50); + expect(target.testColor).toEqual('#FF000080'); - anim.update({duration: 500}, 'blue', Date.now()); - anim.tick(anim._start + 250); - expect(target.testColor).toEqual('#4000BFBF'); + anim.update({duration: 500}, 'blue', Date.now()); + anim.tick(anim._start + 250); + expect(target.testColor).toEqual('#4000BFBF'); - anim.tick(anim._start + 500); - expect(target.testColor).toEqual('blue'); - }); + anim.tick(anim._start + 500); + expect(target.testColor).toEqual('blue'); + }); - it('should not update when finished', function() { - const target = {testColor: 'transparent'}; - const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red'); + it('should not update when finished', function() { + const target = {testColor: 'transparent'}; + const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red'); - anim.tick(anim._start + 100); - expect(target.testColor).toEqual('red'); - expect(anim.active()).toBeFalse(); + anim.tick(anim._start + 100); + expect(target.testColor).toEqual('red'); + expect(anim.active()).toBeFalse(); - anim.update({duration: 500}, 'blue', Date.now()); - expect(anim._duration).toEqual(100); - expect(anim._to).toEqual('red'); - }); + anim.update({duration: 500}, 'blue', Date.now()); + expect(anim._duration).toEqual(100); + expect(anim._to).toEqual('red'); + }); }); diff --git a/test/specs/core.animations.tests.js b/test/specs/core.animations.tests.js index e82b92a73bc..d7b2c193902 100644 --- a/test/specs/core.animations.tests.js +++ b/test/specs/core.animations.tests.js @@ -1,188 +1,188 @@ describe('Chart.animations', function() { - it('should override property collection with property', function() { - const chart = {}; - const anims = new Chart.Animations(chart, { - collection1: { - properties: ['property1', 'property2'], - duration: 1000 - }, - property2: { - duration: 2000 - } - }); - expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000})); - expect(anims._properties.get('property2')).toEqual({duration: 2000}); - }); - - it('should ignore duplicate definitions from collections', function() { - const chart = {}; - const anims = new Chart.Animations(chart, { - collection1: { - properties: ['property1'], - duration: 1000 - }, - collection2: { - properties: ['property1', 'property2'], - duration: 2000 - } - }); - expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000})); - expect(anims._properties.get('property2')).toEqual(jasmine.objectContaining({duration: 2000})); - }); - - it('should not animate undefined options key', function() { - const chart = {}; - const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); - const target = { - value: 1, - options: { - option: 2 - } - }; - expect(anims.update(target, { - options: undefined - })).toBeUndefined(); - }); - - it('should assing options directly, if target does not have previous options', function() { - const chart = {}; - const anims = new Chart.Animations(chart, {option: {duration: 200}}); - const target = {}; - expect(anims.update(target, {options: {option: 1}})).toBeUndefined(); - }); - - it('should clone the target options, if those are shared and new options are not', function() { - const chart = {}; - const anims = new Chart.Animations(chart, {option: {duration: 200}}); - const options = {option: 0, $shared: true}; - const target = {options}; - expect(anims.update(target, {options: {option: 1}})).toBeTrue(); - expect(target.options.$shared).not.toBeTrue(); - expect(target.options !== options).toBeTrue(); - }); - - it('should assign shared options to target after animations complete', function(done) { - const chart = { - draw: function() {}, - options: { - animation: { - debug: false - } - } - }; - const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); - - const target = { - value: 1, - options: { - option: 2 - } - }; - const sharedOpts = {option: 10, $shared: true}; - - expect(anims.update(target, { - options: sharedOpts - })).toBeTrue(); - - expect(target.options !== sharedOpts).toBeTrue(); - - Chart.animator.start(chart); - - setTimeout(function() { - expect(Chart.animator.running(chart)).toBeFalse(); - expect(target.options === sharedOpts).toBeTrue(); - - Chart.animator.remove(chart); - done(); - }, 300); - }); - - it('should not assign shared options to target when animations are cancelled', function(done) { - const chart = { - draw: function() {}, - options: { - animation: { - debug: false - } - } - }; - const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); - - const target = { - value: 1, - options: { - option: 2 - } - }; - const sharedOpts = {option: 10, $shared: true}; - - expect(anims.update(target, { - options: sharedOpts - })).toBeTrue(); - - expect(target.options !== sharedOpts).toBeTrue(); - - Chart.animator.start(chart); - - setTimeout(function() { - expect(Chart.animator.running(chart)).toBeTrue(); - Chart.animator.stop(chart); - expect(Chart.animator.running(chart)).toBeFalse(); - - setTimeout(function() { - expect(target.options === sharedOpts).toBeFalse(); - - Chart.animator.remove(chart); - done(); - }, 250); - }, 50); - }); - - - it('should assign final shared options to target after animations complete', function(done) { - const chart = { - draw: function() {}, - options: { - animation: { - debug: false - } - } - }; - const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); - - const origOpts = {option: 2}; - const target = { - value: 1, - options: origOpts - }; - const sharedOpts = {option: 10, $shared: true}; - const sharedOpts2 = {option: 20, $shared: true}; - - expect(anims.update(target, { - options: sharedOpts - })).toBeTrue(); - - expect(target.options !== sharedOpts).toBeTrue(); - - Chart.animator.start(chart); - - setTimeout(function() { - expect(Chart.animator.running(chart)).toBeTrue(); - - expect(target.options === origOpts).toBeTrue(); - - expect(anims.update(target, { - options: sharedOpts2 - })).toBeUndefined(); - - expect(target.options === origOpts).toBeTrue(); - - setTimeout(function() { - expect(target.options === sharedOpts2).toBeTrue(); - - Chart.animator.remove(chart); - done(); - }, 250); - }, 50); - }); + it('should override property collection with property', function() { + const chart = {}; + const anims = new Chart.Animations(chart, { + collection1: { + properties: ['property1', 'property2'], + duration: 1000 + }, + property2: { + duration: 2000 + } + }); + expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000})); + expect(anims._properties.get('property2')).toEqual({duration: 2000}); + }); + + it('should ignore duplicate definitions from collections', function() { + const chart = {}; + const anims = new Chart.Animations(chart, { + collection1: { + properties: ['property1'], + duration: 1000 + }, + collection2: { + properties: ['property1', 'property2'], + duration: 2000 + } + }); + expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000})); + expect(anims._properties.get('property2')).toEqual(jasmine.objectContaining({duration: 2000})); + }); + + it('should not animate undefined options key', function() { + const chart = {}; + const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); + const target = { + value: 1, + options: { + option: 2 + } + }; + expect(anims.update(target, { + options: undefined + })).toBeUndefined(); + }); + + it('should assing options directly, if target does not have previous options', function() { + const chart = {}; + const anims = new Chart.Animations(chart, {option: {duration: 200}}); + const target = {}; + expect(anims.update(target, {options: {option: 1}})).toBeUndefined(); + }); + + it('should clone the target options, if those are shared and new options are not', function() { + const chart = {}; + const anims = new Chart.Animations(chart, {option: {duration: 200}}); + const options = {option: 0, $shared: true}; + const target = {options}; + expect(anims.update(target, {options: {option: 1}})).toBeTrue(); + expect(target.options.$shared).not.toBeTrue(); + expect(target.options !== options).toBeTrue(); + }); + + it('should assign shared options to target after animations complete', function(done) { + const chart = { + draw: function() {}, + options: { + animation: { + debug: false + } + } + }; + const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); + + const target = { + value: 1, + options: { + option: 2 + } + }; + const sharedOpts = {option: 10, $shared: true}; + + expect(anims.update(target, { + options: sharedOpts + })).toBeTrue(); + + expect(target.options !== sharedOpts).toBeTrue(); + + Chart.animator.start(chart); + + setTimeout(function() { + expect(Chart.animator.running(chart)).toBeFalse(); + expect(target.options === sharedOpts).toBeTrue(); + + Chart.animator.remove(chart); + done(); + }, 300); + }); + + it('should not assign shared options to target when animations are cancelled', function(done) { + const chart = { + draw: function() {}, + options: { + animation: { + debug: false + } + } + }; + const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); + + const target = { + value: 1, + options: { + option: 2 + } + }; + const sharedOpts = {option: 10, $shared: true}; + + expect(anims.update(target, { + options: sharedOpts + })).toBeTrue(); + + expect(target.options !== sharedOpts).toBeTrue(); + + Chart.animator.start(chart); + + setTimeout(function() { + expect(Chart.animator.running(chart)).toBeTrue(); + Chart.animator.stop(chart); + expect(Chart.animator.running(chart)).toBeFalse(); + + setTimeout(function() { + expect(target.options === sharedOpts).toBeFalse(); + + Chart.animator.remove(chart); + done(); + }, 250); + }, 50); + }); + + + it('should assign final shared options to target after animations complete', function(done) { + const chart = { + draw: function() {}, + options: { + animation: { + debug: false + } + } + }; + const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}}); + + const origOpts = {option: 2}; + const target = { + value: 1, + options: origOpts + }; + const sharedOpts = {option: 10, $shared: true}; + const sharedOpts2 = {option: 20, $shared: true}; + + expect(anims.update(target, { + options: sharedOpts + })).toBeTrue(); + + expect(target.options !== sharedOpts).toBeTrue(); + + Chart.animator.start(chart); + + setTimeout(function() { + expect(Chart.animator.running(chart)).toBeTrue(); + + expect(target.options === origOpts).toBeTrue(); + + expect(anims.update(target, { + options: sharedOpts2 + })).toBeUndefined(); + + expect(target.options === origOpts).toBeTrue(); + + setTimeout(function() { + expect(target.options === sharedOpts2).toBeTrue(); + + Chart.animator.remove(chart); + done(); + }, 250); + }, 50); + }); }); diff --git a/test/specs/core.animator.tests.js b/test/specs/core.animator.tests.js index cb05c139bf8..218ff396a5e 100644 --- a/test/specs/core.animator.tests.js +++ b/test/specs/core.animator.tests.js @@ -1,48 +1,48 @@ describe('Chart.animator', function() { - it('should fire onProgress for each draw', function(done) { - let count = 0; - let drawCount = 0; - const progress = (animation) => { - count++; - expect(animation.numSteps).toEqual(250); - expect(animation.currentStep <= 250).toBeTrue(); - }; - acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - animation: { - duration: 250, - onProgress: progress, - onComplete: function() { - expect(count).toEqual(drawCount); - done(); - } - } - }, - plugins: [{ - afterDraw() { - drawCount++; - } - }] - }, { - canvas: { - height: 150, - width: 250 - }, - }); - }); + it('should fire onProgress for each draw', function(done) { + let count = 0; + let drawCount = 0; + const progress = (animation) => { + count++; + expect(animation.numSteps).toEqual(250); + expect(animation.currentStep <= 250).toBeTrue(); + }; + acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + animation: { + duration: 250, + onProgress: progress, + onComplete: function() { + expect(count).toEqual(drawCount); + done(); + } + } + }, + plugins: [{ + afterDraw() { + drawCount++; + } + }] + }, { + canvas: { + height: 150, + width: 250 + }, + }); + }); - it('should not fail when adding no items', function() { - const chart = {}; - Chart.animator.add(chart, undefined); - Chart.animator.add(chart, []); - Chart.animator.start(chart); - expect(Chart.animator.running(chart)).toBeFalse(); - }); + it('should not fail when adding no items', function() { + const chart = {}; + Chart.animator.add(chart, undefined); + Chart.animator.add(chart, []); + Chart.animator.start(chart); + expect(Chart.animator.running(chart)).toBeFalse(); + }); }); diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index 3b6b68db6b9..43a161c94d1 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -1,1754 +1,1754 @@ describe('Chart', function() { - // https://github.com/chartjs/Chart.js/issues/2481 - // See global.deprecations.tests.js for backward compatibility - it('should be defined and prototype of chart instances', function() { - var chart = acquireChart({}); - expect(Chart).toBeDefined(); - expect(Chart instanceof Object).toBeTruthy(); - expect(chart.constructor).toBe(Chart); - expect(chart instanceof Chart).toBeTruthy(); - }); - - it('should throw an error if the canvas is already in use', function() { - var config = { - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3, 4] - }], - labels: ['A', 'B', 'C', 'D'] - } - }; - var chart = acquireChart(config); - var canvas = chart.canvas; - - function createChart() { - return new Chart(canvas, config); - } - - expect(createChart).toThrow(new Error( - 'Canvas is already in use. ' + + // https://github.com/chartjs/Chart.js/issues/2481 + // See global.deprecations.tests.js for backward compatibility + it('should be defined and prototype of chart instances', function() { + var chart = acquireChart({}); + expect(Chart).toBeDefined(); + expect(Chart instanceof Object).toBeTruthy(); + expect(chart.constructor).toBe(Chart); + expect(chart instanceof Chart).toBeTruthy(); + }); + + it('should throw an error if the canvas is already in use', function() { + var config = { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3, 4] + }], + labels: ['A', 'B', 'C', 'D'] + } + }; + var chart = acquireChart(config); + var canvas = chart.canvas; + + function createChart() { + return new Chart(canvas, config); + } + + expect(createChart).toThrow(new Error( + 'Canvas is already in use. ' + 'Chart with ID \'' + chart.id + '\'' + ' must be destroyed before the canvas can be reused.' - )); - - chart.destroy(); - expect(createChart).not.toThrow(); - }); - - describe('config initialization', function() { - it('should create missing config.data properties', function() { - var chart = acquireChart({}); - var data = chart.data; - - expect(data instanceof Object).toBeTruthy(); - expect(data.labels instanceof Array).toBeTruthy(); - expect(data.labels.length).toBe(0); - expect(data.datasets instanceof Array).toBeTruthy(); - expect(data.datasets.length).toBe(0); - }); - - it('should not alter config.data references', function() { - var ds0 = {data: [10, 11, 12, 13]}; - var ds1 = {data: [20, 21, 22, 23]}; - var datasets = [ds0, ds1]; - var labels = [0, 1, 2, 3]; - var data = {labels: labels, datasets: datasets}; - - var chart = acquireChart({ - type: 'line', - data: data - }); - - expect(chart.data).toBe(data); - expect(chart.data.labels).toBe(labels); - expect(chart.data.datasets).toBe(datasets); - expect(chart.data.datasets[0]).toBe(ds0); - expect(chart.data.datasets[1]).toBe(ds1); - expect(chart.data.datasets[0].data).toBe(ds0.data); - expect(chart.data.datasets[1].data).toBe(ds1.data); - }); - - it('should define chart.data as an alias for config.data', function() { - var config = {data: {labels: [], datasets: []}}; - var chart = acquireChart(config); - - expect(chart.data).toBe(config.data); - - chart.data = {labels: [1, 2, 3], datasets: [{data: [4, 5, 6]}]}; - - expect(config.data).toBe(chart.data); - expect(config.data.labels).toEqual([1, 2, 3]); - expect(config.data.datasets[0].data).toEqual([4, 5, 6]); - - config.data = {labels: [7, 8, 9], datasets: [{data: [10, 11, 12]}]}; - - expect(chart.data).toBe(config.data); - expect(chart.data.labels).toEqual([7, 8, 9]); - expect(chart.data.datasets[0].data).toEqual([10, 11, 12]); - }); - - it('should initialize config with default interaction options', function() { - var callback = function() {}; - var defaults = Chart.defaults; - var defaultMode = defaults.controllers.line.interaction.mode; - - defaults.hover.onHover = callback; - defaults.controllers.line.spanGaps = true; - defaults.controllers.line.interaction.mode = 'test'; - - var chart = acquireChart({ - type: 'line' - }); - - var options = chart.options; - expect(options.font.size).toBe(defaults.font.size); - expect(options.showLine).toBe(defaults.controllers.line.showLine); - expect(options.spanGaps).toBe(true); - expect(options.hover.onHover).toBe(callback); - expect(options.hover.mode).toBe('test'); - - defaults.hover.onHover = null; - defaults.controllers.line.spanGaps = false; - defaults.controllers.line.interaction.mode = defaultMode; - }); - - it('should initialize config with default hover options', function() { - var callback = function() {}; - var defaults = Chart.defaults; - - defaults.hover.onHover = callback; - defaults.controllers.line.spanGaps = true; - defaults.controllers.line.hover.mode = 'test'; - - var chart = acquireChart({ - type: 'line' - }); - - var options = chart.options; - expect(options.font.size).toBe(defaults.font.size); - expect(options.showLine).toBe(defaults.controllers.line.showLine); - expect(options.spanGaps).toBe(true); - expect(options.hover.onHover).toBe(callback); - expect(options.hover.mode).toBe('test'); - - defaults.hover.onHover = null; - defaults.controllers.line.spanGaps = false; - delete defaults.controllers.line.hover.mode; - }); - - it('should override default options', function() { - var callback = function() {}; - var defaults = Chart.defaults; - - defaults.hover.onHover = callback; - defaults.controllers.line.hover.mode = 'x-axis'; - defaults.controllers.line.spanGaps = true; - - var chart = acquireChart({ - type: 'line', - options: { - spanGaps: false, - hover: { - mode: 'dataset', - }, - plugins: { - title: { - position: 'bottom' - } - } - } - }); - - var options = chart.options; - expect(options.showLine).toBe(defaults.showLine); - expect(options.spanGaps).toBe(false); - expect(options.hover.mode).toBe('dataset'); - expect(options.plugins.title.position).toBe('bottom'); - - defaults.hover.onHover = null; - delete defaults.controllers.line.hover.mode; - defaults.controllers.line.spanGaps = false; - }); - - it('should override axis positions that are incorrect', function() { - var chart = acquireChart({ - type: 'line', - options: { - scales: { - x: { - position: 'left', - }, - y: { - position: 'bottom' - } - } - } - }); - - var scaleOptions = chart.options.scales; - expect(scaleOptions.x.position).toBe('bottom'); - expect(scaleOptions.y.position).toBe('left'); - }); - - it('should throw an error if the chart type is incorrect', function() { - function createChart() { - acquireChart({ - type: 'area', - data: { - datasets: [{ - label: 'first', - data: [10, 20] - }], - labels: ['0', '1'], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'left', - }, - y: { - type: 'category', - position: 'bottom' - } - } - } - }); - } - expect(createChart).toThrow(new Error('"area" is not a registered controller.')); - }); - - describe('should disable hover', function() { - it('when options.hover=false', function() { - var chart = acquireChart({ - type: 'line', - options: { - hover: false - } - }); - expect(chart.options.hover).toBeFalse(); - }); - - it('when options.interation=false and options.hover is not defined', function() { - var chart = acquireChart({ - type: 'line', - options: { - interaction: false - } - }); - expect(chart.options.hover).toBeFalse(); - }); - - it('when options.interation=false and options.hover is defined', function() { - var chart = acquireChart({ - type: 'line', - options: { - interaction: false, - hover: {mode: 'nearest'} - } - }); - expect(chart.options.hover).toBeFalse(); - }); - }); - - it('should activate element on hover', function(done) { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - } - }); - - var point = chart.getDatasetMeta(0).data[1]; - - afterEvent(chart, 'mousemove', function() { - expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point}]); - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it('should not activate elements when hover is disabled', function(done) { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - hover: false - } - }); - - var point = chart.getDatasetMeta(0).data[1]; - - afterEvent(chart, 'mousemove', function() { - expect(chart.getActiveElements()).toEqual([]); - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); - - describe('when merging scale options', function() { - beforeEach(function() { - Chart.helpers.merge(Chart.defaults.scale, { - _jasmineCheckA: 'a0', - _jasmineCheckB: 'b0', - _jasmineCheckC: 'c0' - }); - - Chart.helpers.merge(Chart.defaults.scales.logarithmic, { - _jasmineCheckB: 'b1', - _jasmineCheckC: 'c1', - }); - }); - - afterEach(function() { - delete Chart.defaults.scale._jasmineCheckA; - delete Chart.defaults.scale._jasmineCheckB; - delete Chart.defaults.scale._jasmineCheckC; - delete Chart.defaults.scales.logarithmic._jasmineCheckB; - delete Chart.defaults.scales.logarithmic._jasmineCheckC; - }); - - it('should default to "category" for x scales and "linear" for y scales', function() { - var chart = acquireChart({ - type: 'line', - options: { - scales: { - xFoo0: {}, - xFoo1: {}, - yBar0: {}, - yBar1: {}, - } - } - }); - - expect(chart.scales.xFoo0.type).toBe('category'); - expect(chart.scales.xFoo1.type).toBe('category'); - expect(chart.scales.yBar0.type).toBe('linear'); - expect(chart.scales.yBar1.type).toBe('linear'); - }); - - it('should correctly apply defaults on central scale', function() { - var chart = acquireChart({ - type: 'line', - options: { - scales: { - foo: { - type: 'logarithmic', - _jasmineCheckC: 'c2', - _jasmineCheckD: 'd2' - } - } - } - }); - - // let's check a few values from the user options and defaults - - expect(chart.scales.foo.type).toBe('logarithmic'); - expect(chart.scales.foo.options).toEqual(chart.options.scales.foo); - expect(chart.scales.foo.options).toEqual( - jasmine.objectContaining({ - _jasmineCheckA: 'a0', - _jasmineCheckB: 'b1', - _jasmineCheckC: 'c2', - _jasmineCheckD: 'd2' - })); - }); - - it('should correctly apply defaults on xy scales', function() { - var chart = acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'logarithmic', - _jasmineCheckC: 'c2', - _jasmineCheckD: 'd2' - }, - y: { - type: 'time', - _jasmineCheckC: 'c2', - _jasmineCheckE: 'e2' - } - } - } - }); - - expect(chart.scales.x.type).toBe('logarithmic'); - expect(chart.scales.x.options).toBe(chart.options.scales.x); - expect(chart.scales.x.options).toEqual( - jasmine.objectContaining({ - _jasmineCheckA: 'a0', - _jasmineCheckB: 'b1', - _jasmineCheckC: 'c2', - _jasmineCheckD: 'd2' - })); - - expect(chart.scales.y.type).toBe('time'); - expect(chart.scales.y.options).toBe(chart.options.scales.y); - expect(chart.scales.y.options).toEqual( - jasmine.objectContaining({ - _jasmineCheckA: 'a0', - _jasmineCheckB: 'b0', - _jasmineCheckC: 'c2', - _jasmineCheckE: 'e2' - })); - }); - - it('should not alter defaults when merging config', function() { - var chart = acquireChart({ - type: 'line', - options: { - _jasmineCheck: 42, - scales: { - x: { - type: 'linear', - _jasmineCheck: 42, - }, - y: { - type: 'category', - _jasmineCheck: 42, - } - } - } - }); - - expect(chart.options._jasmineCheck).toBeDefined(); - expect(chart.scales.x.options._jasmineCheck).toBeDefined(); - expect(chart.scales.y.options._jasmineCheck).toBeDefined(); - - expect(Chart.defaults.controllers.line._jasmineCheck).not.toBeDefined(); - expect(Chart.defaults._jasmineCheck).not.toBeDefined(); - expect(Chart.defaults.scales.linear._jasmineCheck).not.toBeDefined(); - expect(Chart.defaults.scales.category._jasmineCheck).not.toBeDefined(); - }); - }); - - describe('Updating options', function() { - it('update should result to same set of options as construct', function() { - var chart = acquireChart({ - type: 'line', - data: [], - options: { - animation: false, - locale: 'en-US', - responsive: false - } - }); - const options = chart.options; - chart.options = { - animation: false, - locale: 'en-US', - responsive: false - }; - chart.update(); - expect(chart.options).toEqual(jasmine.objectContaining(options)); - }); - }); - - describe('config.options.responsive: true (maintainAspectRatio: false)', function() { - it('should fill parent width and height', function() { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: 'width: 150px; height: 245px' - }, - wrapper: { - style: 'width: 300px; height: 350px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - }); - - it('should resize the canvas when parent width changes', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 300px; height: 350px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 350, - rw: 455, rh: 350, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 150, dh: 350, - rw: 150, rh: 350, - }); - - done(); - }); - wrapper.style.width = '150px'; - }); - wrapper.style.width = '455px'; - }); - - it('should restore the original size when parent became invisible', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 300px; height: 350px; position: relative' - } - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - var original = chart.resize; - chart.resize = function() { - fail('resize should not have been called'); - }; - - var wrapper = chart.canvas.parentNode; - wrapper.style.display = 'none'; - - setTimeout(function() { - expect(wrapper.clientWidth).toEqual(0); - expect(wrapper.clientHeight).toEqual(0); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - chart.resize = original; - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - done(); - }); - wrapper.style.display = 'block'; - }, 200); - }); - }); - - it('should resize the canvas when parent is RTL and width changes', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 300px; height: 350px; position: relative; direction: rtl' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 350, - rw: 455, rh: 350, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 150, dh: 350, - rw: 150, rh: 350, - }); - - done(); - }); - wrapper.style.width = '150px'; - }); - wrapper.style.width = '455px'; - }); - - it('should resize the canvas when parent height changes', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 300px; height: 350px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 350, - rw: 300, rh: 350, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 300, dh: 455, - rw: 300, rh: 455, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 300, dh: 150, - rw: 300, rh: 150, - }); - - done(); - }); - wrapper.style.height = '150px'; - }); - wrapper.style.height = '455px'; - }); - - it('should not include parent padding when resizing the canvas', function(done) { - var chart = acquireChart({ - type: 'line', - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'padding: 50px; width: 320px; height: 350px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 350, - rw: 320, rh: 350, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 355, - rw: 455, rh: 355, - }); - - done(); - }); - wrapper.style.height = '355px'; - wrapper.style.width = '455px'; - }); - - it('should resize the canvas when the canvas display style changes from "none" to "block"', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: 'display: none;' - }, - wrapper: { - style: 'width: 320px; height: 350px' - } - }); - - var canvas = chart.canvas; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 320, dh: 350, - rw: 320, rh: 350, - }); - - done(); - }); - canvas.style.display = 'block'; - }); - - it('should resize the canvas when the wrapper display style changes from "none" to "block"', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'display: none; width: 460px; height: 380px' - } - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 460, dh: 380, - rw: 460, rh: 380, - }); - - done(); - }); - wrapper.style.display = 'block'; - }); - - // https://github.com/chartjs/Chart.js/issues/5485 - it('should resize the canvas when the devicePixelRatio changes', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false, - devicePixelRatio: 1 - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 400px; height: 200px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 400, dh: 200, - rw: 400, rh: 200, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 400, dh: 200, - rw: 800, rh: 400, - }); - - done(); - }); - chart.options.devicePixelRatio = 2; - chart.resize(); - }); - - // https://github.com/chartjs/Chart.js/issues/3790 - it('should resize the canvas if attached to the DOM after construction', function(done) { - var canvas = document.createElement('canvas'); - var wrapper = document.createElement('div'); - var body = window.document.body; - var chart = new Chart(canvas, { - type: 'line', - options: { - responsive: true, - maintainAspectRatio: false - } - }); - - expect(chart).toBeChartOfSize({ - dw: 0, dh: 0, - rw: 0, rh: 0, - }); - expect(chart.chartArea).toBeUndefined(); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 355, - rw: 455, rh: 355, - }); - - expect(chart.chartArea).not.toBeUndefined(); - - body.removeChild(wrapper); - chart.destroy(); - done(); - }); - - wrapper.style.cssText = 'width: 455px; height: 355px'; - wrapper.appendChild(canvas); - body.appendChild(wrapper); - }); - - it('should resize the canvas when attached to a different parent', function(done) { - var canvas = document.createElement('canvas'); - var wrapper = document.createElement('div'); - var body = window.document.body; - var chart = new Chart(canvas, { - type: 'line', - options: { - responsive: true, - maintainAspectRatio: false - } - }); - - expect(chart).toBeChartOfSize({ - dw: 0, dh: 0, - rw: 0, rh: 0, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 355, - rw: 455, rh: 355, - }); - - var target = document.createElement('div'); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 640, dh: 480, - rw: 640, rh: 480, - }); - - body.removeChild(wrapper); - body.removeChild(target); - chart.destroy(); - done(); - }); - - target.style.cssText = 'width: 640px; height: 480px'; - target.appendChild(canvas); - body.appendChild(target); - }); - - wrapper.style.cssText = 'width: 455px; height: 355px'; - wrapper.appendChild(canvas); - body.appendChild(wrapper); - }); - - // https://github.com/chartjs/Chart.js/issues/3521 - it('should resize the canvas after the wrapper has been re-attached to the DOM', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: false - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 320px; height: 350px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 350, - rw: 320, rh: 350, - }); - - var wrapper = chart.canvas.parentNode; - var parent = wrapper.parentNode; - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 320, dh: 355, - rw: 320, rh: 355, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 355, - rw: 455, rh: 355, - }); - - done(); - }); - - parent.removeChild(wrapper); - wrapper.style.width = '455px'; - parent.appendChild(wrapper); - }); - - parent.removeChild(wrapper); - parent.appendChild(wrapper); - wrapper.style.height = '355px'; - }); - - // https://github.com/chartjs/Chart.js/issues/4737 - it('should resize the canvas when re-creating the chart', function(done) { - var chart = acquireChart({ - options: { - responsive: true - } - }, { - wrapper: { - style: 'width: 320px' - } - }); - - var wrapper = chart.canvas.parentNode; - - waitForResize(chart, function() { - var canvas = chart.canvas; - expect(chart).toBeChartOfSize({ - dw: 320, dh: 320, - rw: 320, rh: 320, - }); - - chart.destroy(); - chart = new Chart(canvas, { - type: 'line', - options: { - responsive: true - } - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 455, dh: 455, - rw: 455, rh: 455, - }); - - chart.destroy(); - window.document.body.removeChild(wrapper); - done(); - }); - canvas.parentNode.style.width = '455px'; - }); - }); - }); - - describe('config.options.responsive: true (maintainAspectRatio: true)', function() { - it('should resize the canvas with correct aspect ratio when parent width changes', function(done) { - var chart = acquireChart({ - type: 'line', // AR == 2 - options: { - responsive: true, - maintainAspectRatio: true - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 300px; height: 350px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 150, - rw: 300, rh: 150, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 450, dh: 225, - rw: 450, rh: 225, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 150, dh: 75, - rw: 150, rh: 75, - }); - - done(); - }); - wrapper.style.width = '150px'; - }); - wrapper.style.width = '450px'; - }); - - it('should not resize the canvas when parent height changes', function(done) { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: true - } - }, { - canvas: { - style: '' - }, - wrapper: { - style: 'width: 320px; height: 350px; position: relative' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 160, - rw: 320, rh: 160, - }); - - var wrapper = chart.canvas.parentNode; - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 320, dh: 160, - rw: 320, rh: 160, - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 320, dh: 160, - rw: 320, rh: 160, - }); - - done(); - }); - wrapper.style.height = '150px'; - }); - wrapper.style.height = '455px'; - }); - }); - - describe('Retina scale (a.k.a. device pixel ratio)', function() { - beforeEach(function() { - this.devicePixelRatio = window.devicePixelRatio; - window.devicePixelRatio = 3; - }); - - afterEach(function() { - window.devicePixelRatio = this.devicePixelRatio; - }); - - // see https://github.com/chartjs/Chart.js/issues/3575 - it ('should scale the render size but not the "implicit" display size', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - width: 320, - height: 240, - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 240, - rw: 960, rh: 720, - }); - }); - - it ('should scale the render size but not the "explicit" display size', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 320px; height: 240px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 240, - rw: 960, rh: 720, - }); - }); - }); - - describe('config.options.devicePixelRatio', function() { - beforeEach(function() { - this.devicePixelRatio = window.devicePixelRatio; - window.devicePixelRatio = 1; - }); - - afterEach(function() { - window.devicePixelRatio = this.devicePixelRatio; - }); - - // see https://github.com/chartjs/Chart.js/issues/3575 - it ('should scale the render size but not the "implicit" display size', function() { - var chart = acquireChart({ - options: { - responsive: false, - devicePixelRatio: 3 - } - }, { - canvas: { - width: 320, - height: 240, - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 240, - rw: 960, rh: 720, - }); - }); - - it ('should scale the render size but not the "explicit" display size', function() { - var chart = acquireChart({ - options: { - responsive: false, - devicePixelRatio: 3 - } - }, { - canvas: { - style: 'width: 320px; height: 240px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 320, dh: 240, - rw: 960, rh: 720, - }); - }); - }); - - describe('controller.reset', function() { - it('should reset the chart elements', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 0] - }] - }, - options: { - responsive: true - } - }); - - var meta = chart.getDatasetMeta(0); - - // Verify that points are at their initial correct location, - // then we will reset and see that they moved - expect(meta.data[0].y).toBeCloseToPixel(333); - expect(meta.data[1].y).toBeCloseToPixel(183); - expect(meta.data[2].y).toBeCloseToPixel(32); - expect(meta.data[3].y).toBeCloseToPixel(482); - - chart.reset(); - - // For a line chart, the animation state is the bottom - expect(meta.data[0].y).toBeCloseToPixel(482); - expect(meta.data[1].y).toBeCloseToPixel(482); - expect(meta.data[2].y).toBeCloseToPixel(482); - expect(meta.data[3].y).toBeCloseToPixel(482); - }); - }); - - describe('config update', function() { - it ('should update options', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true - } - }); - - chart.options = { - responsive: false, - scales: { - y: { - min: 0, - max: 10 - } - } - }; - chart.update(); - - var yScale = chart.scales.y; - expect(yScale.options.min).toBe(0); - expect(yScale.options.max).toBe(10); - }); - - it ('should update scales options', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true - } - }); - - chart.options.scales.y.min = 0; - chart.options.scales.y.max = 10; - chart.update(); - - var yScale = chart.scales.y; - expect(yScale.options.min).toBe(0); - expect(yScale.options.max).toBe(10); - }); - - it ('should update scales options from new object', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true - } - }); - - var newScalesConfig = { - y: { - min: 0, - max: 10 - } - }; - chart.options.scales = newScalesConfig; - - chart.update(); - - var yScale = chart.scales.y; - expect(yScale.options.min).toBe(0); - expect(yScale.options.max).toBe(10); - }); - - it ('should remove discarded scale', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true, - scales: { - y: { - min: 0, - max: 10 - } - } - } - }); - - var newScalesConfig = { - y: { - min: 0, - max: 10 - } - }; - chart.options.scales = newScalesConfig; - - chart.update(); - - var yScale = chart.scales.yAxis0; - expect(yScale).toBeUndefined(); - var newyScale = chart.scales.y; - expect(newyScale.options.min).toBe(0); - expect(newyScale.options.max).toBe(10); - }); - - it ('should update tooltip options', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true - } - }); - - var newTooltipConfig = { - mode: 'dataset', - intersect: false - }; - chart.options.plugins.tooltip = newTooltipConfig; - - chart.update(); - expect(chart.tooltip.options).toEqual(jasmine.objectContaining(newTooltipConfig)); - }); - - it ('should update the tooltip on update', function(done) { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true, - tooltip: { - mode: 'nearest' - } - } - }); - - // Trigger an event over top of a point to - // put an item into the tooltip - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - - expect(chart._active[0].element).toEqual(point); - expect(tooltip._active[0].element).toEqual(point); - - // Update and confirm tooltip is updated - chart.update(); - expect(chart._active[0].element).toEqual(point); - expect(tooltip._active[0].element).toEqual(point); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it ('should update the metadata', function() { - var cfg = { - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - type: 'line', - data: [10, 20, 30, 0] - }] - }, - options: { - responsive: true, - scales: { - x: { - type: 'category' - }, - y: { - type: 'linear', - scaleLabel: { - display: true, - labelString: 'Value' - } - } - } - } - }; - var chart = acquireChart(cfg); - var meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('line'); - - // change the dataset to bar and check that meta was updated - chart.config.data.datasets[0].type = 'bar'; - chart.update(); - meta = chart.getDatasetMeta(0); - expect(meta.type).toBe('bar'); - }); - }); - - describe('plugin.extensions', function() { - var hooks = { - install: ['install'], - uninstall: ['uninstall'], - init: [ - 'beforeInit', - 'resize', - 'afterInit' - ], - start: ['start'], - stop: ['stop'], - update: [ - 'beforeUpdate', - 'beforeLayout', - 'beforeDataLimits', - 'afterDataLimits', - 'beforeBuildTicks', - 'afterBuildTicks', - 'beforeDataLimits', - 'afterDataLimits', - 'beforeBuildTicks', - 'afterBuildTicks', - 'afterLayout', - 'beforeDatasetsUpdate', - 'beforeDatasetUpdate', - 'afterDatasetUpdate', - 'afterDatasetsUpdate', - 'afterUpdate', - ], - render: [ - 'beforeRender', - 'beforeDraw', - 'beforeDatasetsDraw', - 'beforeDatasetDraw', - 'afterDatasetDraw', - 'afterDatasetsDraw', - 'beforeTooltipDraw', - 'afterTooltipDraw', - 'afterDraw', - 'afterRender', - ], - resize: [ - 'resize' - ], - destroy: [ - 'destroy' - ] - }; - - it ('should notify plugin in correct order', function(done) { - var plugin = this.plugin = {}; - var sequence = []; - - Object.keys(hooks).forEach(function(group) { - hooks[group].forEach(function(name) { - plugin[name] = function() { - sequence.push(name); - }; - }); - }); - - var chart = window.acquireChart({ - type: 'line', - data: {datasets: [{}]}, - plugins: [plugin], - options: { - responsive: true - } - }, { - wrapper: { - style: 'width: 300px' - } - }); - - waitForResize(chart, function() { - chart.destroy(); - - expect(sequence).toEqual([].concat( - hooks.install, - hooks.start, - hooks.init, - hooks.update, - hooks.render, - hooks.resize, - hooks.update, - hooks.render, - hooks.destroy, - hooks.stop, - hooks.uninstall - )); - - done(); - }); - chart.canvas.parentNode.style.width = '400px'; - }); - - it ('should notify initially disabled plugin in correct order', function() { - var plugin = this.plugin = {id: 'plugin'}; - var sequence = []; - - Object.keys(hooks).forEach(function(group) { - hooks[group].forEach(function(name) { - plugin[name] = function() { - sequence.push(name); - }; - }); - }); - - var chart = window.acquireChart({ - type: 'line', - data: {datasets: [{}]}, - plugins: [plugin], - options: { - plugins: { - plugin: false - } - } - }); - - expect(sequence).toEqual([].concat( - hooks.install - )); - - sequence = []; - chart.options.plugins.plugin = true; - chart.update(); - - expect(sequence).toEqual([].concat( - hooks.start, - hooks.update, - hooks.render - )); - - sequence = []; - chart.options.plugins.plugin = false; - chart.update(); - - expect(sequence).toEqual(hooks.stop); - - sequence = []; - chart.destroy(); - - expect(sequence).toEqual(hooks.uninstall); - }); - - it('should not notify before/afterDatasetDraw if dataset is hidden', function() { - var sequence = []; - var plugin = this.plugin = { - beforeDatasetDraw: function(chart, args) { - sequence.push('before-' + args.index); - }, - afterDatasetDraw: function(chart, args) { - sequence.push('after-' + args.index); - } - }; - - window.acquireChart({ - type: 'line', - data: {datasets: [{}, {hidden: true}, {}]}, - plugins: [plugin] - }); - - expect(sequence).toEqual([ - 'before-2', 'after-2', - 'before-0', 'after-0' - ]); - }); - }); - - describe('metasets', function() { - beforeEach(function() { - this.chart = acquireChart({ - type: 'line', - data: { - datasets: [ - {label: '1', order: 2}, - {label: '2', order: 1}, - {label: '3', order: 4}, - {label: '4', order: 3}, - ] - } - }); - }); - afterEach(function() { - const metasets = this.chart._metasets; - expect(metasets.length).toEqual(this.chart.data.datasets.length); - for (let i = 0; i < metasets.length; i++) { - expect(metasets[i].index).toEqual(i); - expect(metasets[i]._dataset).toEqual(this.chart.data.datasets[i]); - } - }); - it('should build metasets array in order', function() { - const metasets = this.chart._metasets; - expect(metasets[0].order).toEqual(2); - expect(metasets[1].order).toEqual(1); - expect(metasets[2].order).toEqual(4); - expect(metasets[3].order).toEqual(3); - }); - it('should build sorted metasets array in correct order', function() { - const metasets = this.chart._sortedMetasets; - expect(metasets[0].order).toEqual(1); - expect(metasets[1].order).toEqual(2); - expect(metasets[2].order).toEqual(3); - expect(metasets[3].order).toEqual(4); - }); - it('should be moved when datasets are removed from begining', function() { - this.chart.data.datasets.splice(0, 2); - this.chart.update(); - const metasets = this.chart._metasets; - expect(metasets[0].order).toEqual(4); - expect(metasets[1].order).toEqual(3); - }); - it('should be moved when datasets are removed from middle', function() { - this.chart.data.datasets.splice(1, 2); - this.chart.update(); - const metasets = this.chart._metasets; - expect(metasets[0].order).toEqual(2); - expect(metasets[1].order).toEqual(3); - }); - it('should be moved when datasets are inserted', function() { - this.chart.data.datasets.splice(1, 0, {label: '1.5', order: 5}); - this.chart.update(); - const metasets = this.chart._metasets; - expect(metasets[0].order).toEqual(2); - expect(metasets[1].order).toEqual(5); - expect(metasets[2].order).toEqual(1); - expect(metasets[3].order).toEqual(4); - expect(metasets[4].order).toEqual(3); - }); - it('should be replaced when dataset is replaced', function() { - this.chart.data.datasets.splice(1, 1, {label: '1.5', order: 5}); - this.chart.update(); - const metasets = this.chart._metasets; - expect(metasets[0].order).toEqual(2); - expect(metasets[1].order).toEqual(5); - expect(metasets[2].order).toEqual(4); - expect(metasets[3].order).toEqual(3); - }); - }); - - describe('data visibility', function() { - it('should hide a dataset', function() { - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 1, 2] - }], - labels: ['a', 'b', 'c'] - } - }); - - chart.setDatasetVisibility(0, false); - - var meta = chart.getDatasetMeta(0); - expect(meta.hidden).toBe(true); - }); - - it('should toggle data visibility by index', function() { - var chart = acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [1, 2, 3] - }] - } - }); - - expect(chart.getDataVisibility(1)).toBe(true); - - chart.toggleDataVisibility(1); - expect(chart.getDataVisibility(1)).toBe(false); - - chart.update(); - expect(chart.getDataVisibility(1)).toBe(false); - }); - }); - - describe('isDatasetVisible', function() { - it('should return false if index is out of bounds', function() { - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, 1, 2] - }], - labels: ['a', 'b', 'c'] - } - }); - - expect(chart.isDatasetVisible(1)).toBe(false); - }); - }); - - describe('getChart', function() { - it('should get the chart from the canvas ID', function() { - var chart = acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [1, 2, 3] - }] - } - }); - chart.canvas.id = 'myID'; - - expect(Chart.getChart('myID')).toBe(chart); - }); - - it('should get the chart from an HTMLCanvasElement', function() { - var chart = acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [1, 2, 3] - }] - } - }); - expect(Chart.getChart(chart.canvas)).toBe(chart); - }); - - it('should get the chart from an CanvasRenderingContext2D', function() { - var chart = acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [1, 2, 3] - }] - } - }); - expect(Chart.getChart(chart.ctx)).toBe(chart); - }); - - it('should return undefined when a chart is not found or bad data is provided', function() { - expect(Chart.getChart(1)).toBeUndefined(); - }); - }); - - describe('active elements', function() { - it('should set the active elements', function() { - var chart = acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [1, 2, 3], - borderColor: 'red', - hoverBorderColor: 'blue', - }] - } - }); - - const meta = chart.getDatasetMeta(0); - let props = meta.data[0].getProps(['borderColor']); - expect(props.options.borderColor).toEqual('red'); - - chart.setActiveElements([{ - datasetIndex: 0, - index: 0, - }]); - - props = meta.data[0].getProps(['borderColor']); - expect(props.options.borderColor).toEqual('blue'); - - const active = chart.getActiveElements(); - expect(active.length).toEqual(1); - expect(active[0].element).toBe(meta.data[0]); - }); - }); + )); + + chart.destroy(); + expect(createChart).not.toThrow(); + }); + + describe('config initialization', function() { + it('should create missing config.data properties', function() { + var chart = acquireChart({}); + var data = chart.data; + + expect(data instanceof Object).toBeTruthy(); + expect(data.labels instanceof Array).toBeTruthy(); + expect(data.labels.length).toBe(0); + expect(data.datasets instanceof Array).toBeTruthy(); + expect(data.datasets.length).toBe(0); + }); + + it('should not alter config.data references', function() { + var ds0 = {data: [10, 11, 12, 13]}; + var ds1 = {data: [20, 21, 22, 23]}; + var datasets = [ds0, ds1]; + var labels = [0, 1, 2, 3]; + var data = {labels: labels, datasets: datasets}; + + var chart = acquireChart({ + type: 'line', + data: data + }); + + expect(chart.data).toBe(data); + expect(chart.data.labels).toBe(labels); + expect(chart.data.datasets).toBe(datasets); + expect(chart.data.datasets[0]).toBe(ds0); + expect(chart.data.datasets[1]).toBe(ds1); + expect(chart.data.datasets[0].data).toBe(ds0.data); + expect(chart.data.datasets[1].data).toBe(ds1.data); + }); + + it('should define chart.data as an alias for config.data', function() { + var config = {data: {labels: [], datasets: []}}; + var chart = acquireChart(config); + + expect(chart.data).toBe(config.data); + + chart.data = {labels: [1, 2, 3], datasets: [{data: [4, 5, 6]}]}; + + expect(config.data).toBe(chart.data); + expect(config.data.labels).toEqual([1, 2, 3]); + expect(config.data.datasets[0].data).toEqual([4, 5, 6]); + + config.data = {labels: [7, 8, 9], datasets: [{data: [10, 11, 12]}]}; + + expect(chart.data).toBe(config.data); + expect(chart.data.labels).toEqual([7, 8, 9]); + expect(chart.data.datasets[0].data).toEqual([10, 11, 12]); + }); + + it('should initialize config with default interaction options', function() { + var callback = function() {}; + var defaults = Chart.defaults; + var defaultMode = defaults.controllers.line.interaction.mode; + + defaults.hover.onHover = callback; + defaults.controllers.line.spanGaps = true; + defaults.controllers.line.interaction.mode = 'test'; + + var chart = acquireChart({ + type: 'line' + }); + + var options = chart.options; + expect(options.font.size).toBe(defaults.font.size); + expect(options.showLine).toBe(defaults.controllers.line.showLine); + expect(options.spanGaps).toBe(true); + expect(options.hover.onHover).toBe(callback); + expect(options.hover.mode).toBe('test'); + + defaults.hover.onHover = null; + defaults.controllers.line.spanGaps = false; + defaults.controllers.line.interaction.mode = defaultMode; + }); + + it('should initialize config with default hover options', function() { + var callback = function() {}; + var defaults = Chart.defaults; + + defaults.hover.onHover = callback; + defaults.controllers.line.spanGaps = true; + defaults.controllers.line.hover.mode = 'test'; + + var chart = acquireChart({ + type: 'line' + }); + + var options = chart.options; + expect(options.font.size).toBe(defaults.font.size); + expect(options.showLine).toBe(defaults.controllers.line.showLine); + expect(options.spanGaps).toBe(true); + expect(options.hover.onHover).toBe(callback); + expect(options.hover.mode).toBe('test'); + + defaults.hover.onHover = null; + defaults.controllers.line.spanGaps = false; + delete defaults.controllers.line.hover.mode; + }); + + it('should override default options', function() { + var callback = function() {}; + var defaults = Chart.defaults; + + defaults.hover.onHover = callback; + defaults.controllers.line.hover.mode = 'x-axis'; + defaults.controllers.line.spanGaps = true; + + var chart = acquireChart({ + type: 'line', + options: { + spanGaps: false, + hover: { + mode: 'dataset', + }, + plugins: { + title: { + position: 'bottom' + } + } + } + }); + + var options = chart.options; + expect(options.showLine).toBe(defaults.showLine); + expect(options.spanGaps).toBe(false); + expect(options.hover.mode).toBe('dataset'); + expect(options.plugins.title.position).toBe('bottom'); + + defaults.hover.onHover = null; + delete defaults.controllers.line.hover.mode; + defaults.controllers.line.spanGaps = false; + }); + + it('should override axis positions that are incorrect', function() { + var chart = acquireChart({ + type: 'line', + options: { + scales: { + x: { + position: 'left', + }, + y: { + position: 'bottom' + } + } + } + }); + + var scaleOptions = chart.options.scales; + expect(scaleOptions.x.position).toBe('bottom'); + expect(scaleOptions.y.position).toBe('left'); + }); + + it('should throw an error if the chart type is incorrect', function() { + function createChart() { + acquireChart({ + type: 'area', + data: { + datasets: [{ + label: 'first', + data: [10, 20] + }], + labels: ['0', '1'], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'left', + }, + y: { + type: 'category', + position: 'bottom' + } + } + } + }); + } + expect(createChart).toThrow(new Error('"area" is not a registered controller.')); + }); + + describe('should disable hover', function() { + it('when options.hover=false', function() { + var chart = acquireChart({ + type: 'line', + options: { + hover: false + } + }); + expect(chart.options.hover).toBeFalse(); + }); + + it('when options.interation=false and options.hover is not defined', function() { + var chart = acquireChart({ + type: 'line', + options: { + interaction: false + } + }); + expect(chart.options.hover).toBeFalse(); + }); + + it('when options.interation=false and options.hover is defined', function() { + var chart = acquireChart({ + type: 'line', + options: { + interaction: false, + hover: {mode: 'nearest'} + } + }); + expect(chart.options.hover).toBeFalse(); + }); + }); + + it('should activate element on hover', function(done) { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + } + }); + + var point = chart.getDatasetMeta(0).data[1]; + + afterEvent(chart, 'mousemove', function() { + expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point}]); + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it('should not activate elements when hover is disabled', function(done) { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + hover: false + } + }); + + var point = chart.getDatasetMeta(0).data[1]; + + afterEvent(chart, 'mousemove', function() { + expect(chart.getActiveElements()).toEqual([]); + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); + + describe('when merging scale options', function() { + beforeEach(function() { + Chart.helpers.merge(Chart.defaults.scale, { + _jasmineCheckA: 'a0', + _jasmineCheckB: 'b0', + _jasmineCheckC: 'c0' + }); + + Chart.helpers.merge(Chart.defaults.scales.logarithmic, { + _jasmineCheckB: 'b1', + _jasmineCheckC: 'c1', + }); + }); + + afterEach(function() { + delete Chart.defaults.scale._jasmineCheckA; + delete Chart.defaults.scale._jasmineCheckB; + delete Chart.defaults.scale._jasmineCheckC; + delete Chart.defaults.scales.logarithmic._jasmineCheckB; + delete Chart.defaults.scales.logarithmic._jasmineCheckC; + }); + + it('should default to "category" for x scales and "linear" for y scales', function() { + var chart = acquireChart({ + type: 'line', + options: { + scales: { + xFoo0: {}, + xFoo1: {}, + yBar0: {}, + yBar1: {}, + } + } + }); + + expect(chart.scales.xFoo0.type).toBe('category'); + expect(chart.scales.xFoo1.type).toBe('category'); + expect(chart.scales.yBar0.type).toBe('linear'); + expect(chart.scales.yBar1.type).toBe('linear'); + }); + + it('should correctly apply defaults on central scale', function() { + var chart = acquireChart({ + type: 'line', + options: { + scales: { + foo: { + type: 'logarithmic', + _jasmineCheckC: 'c2', + _jasmineCheckD: 'd2' + } + } + } + }); + + // let's check a few values from the user options and defaults + + expect(chart.scales.foo.type).toBe('logarithmic'); + expect(chart.scales.foo.options).toEqual(chart.options.scales.foo); + expect(chart.scales.foo.options).toEqual( + jasmine.objectContaining({ + _jasmineCheckA: 'a0', + _jasmineCheckB: 'b1', + _jasmineCheckC: 'c2', + _jasmineCheckD: 'd2' + })); + }); + + it('should correctly apply defaults on xy scales', function() { + var chart = acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'logarithmic', + _jasmineCheckC: 'c2', + _jasmineCheckD: 'd2' + }, + y: { + type: 'time', + _jasmineCheckC: 'c2', + _jasmineCheckE: 'e2' + } + } + } + }); + + expect(chart.scales.x.type).toBe('logarithmic'); + expect(chart.scales.x.options).toBe(chart.options.scales.x); + expect(chart.scales.x.options).toEqual( + jasmine.objectContaining({ + _jasmineCheckA: 'a0', + _jasmineCheckB: 'b1', + _jasmineCheckC: 'c2', + _jasmineCheckD: 'd2' + })); + + expect(chart.scales.y.type).toBe('time'); + expect(chart.scales.y.options).toBe(chart.options.scales.y); + expect(chart.scales.y.options).toEqual( + jasmine.objectContaining({ + _jasmineCheckA: 'a0', + _jasmineCheckB: 'b0', + _jasmineCheckC: 'c2', + _jasmineCheckE: 'e2' + })); + }); + + it('should not alter defaults when merging config', function() { + var chart = acquireChart({ + type: 'line', + options: { + _jasmineCheck: 42, + scales: { + x: { + type: 'linear', + _jasmineCheck: 42, + }, + y: { + type: 'category', + _jasmineCheck: 42, + } + } + } + }); + + expect(chart.options._jasmineCheck).toBeDefined(); + expect(chart.scales.x.options._jasmineCheck).toBeDefined(); + expect(chart.scales.y.options._jasmineCheck).toBeDefined(); + + expect(Chart.defaults.controllers.line._jasmineCheck).not.toBeDefined(); + expect(Chart.defaults._jasmineCheck).not.toBeDefined(); + expect(Chart.defaults.scales.linear._jasmineCheck).not.toBeDefined(); + expect(Chart.defaults.scales.category._jasmineCheck).not.toBeDefined(); + }); + }); + + describe('Updating options', function() { + it('update should result to same set of options as construct', function() { + var chart = acquireChart({ + type: 'line', + data: [], + options: { + animation: false, + locale: 'en-US', + responsive: false + } + }); + const options = chart.options; + chart.options = { + animation: false, + locale: 'en-US', + responsive: false + }; + chart.update(); + expect(chart.options).toEqual(jasmine.objectContaining(options)); + }); + }); + + describe('config.options.responsive: true (maintainAspectRatio: false)', function() { + it('should fill parent width and height', function() { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: 'width: 150px; height: 245px' + }, + wrapper: { + style: 'width: 300px; height: 350px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + }); + + it('should resize the canvas when parent width changes', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 300px; height: 350px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 350, + rw: 455, rh: 350, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 150, dh: 350, + rw: 150, rh: 350, + }); + + done(); + }); + wrapper.style.width = '150px'; + }); + wrapper.style.width = '455px'; + }); + + it('should restore the original size when parent became invisible', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 300px; height: 350px; position: relative' + } + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + var original = chart.resize; + chart.resize = function() { + fail('resize should not have been called'); + }; + + var wrapper = chart.canvas.parentNode; + wrapper.style.display = 'none'; + + setTimeout(function() { + expect(wrapper.clientWidth).toEqual(0); + expect(wrapper.clientHeight).toEqual(0); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + chart.resize = original; + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + done(); + }); + wrapper.style.display = 'block'; + }, 200); + }); + }); + + it('should resize the canvas when parent is RTL and width changes', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 300px; height: 350px; position: relative; direction: rtl' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 350, + rw: 455, rh: 350, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 150, dh: 350, + rw: 150, rh: 350, + }); + + done(); + }); + wrapper.style.width = '150px'; + }); + wrapper.style.width = '455px'; + }); + + it('should resize the canvas when parent height changes', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 300px; height: 350px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 350, + rw: 300, rh: 350, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 300, dh: 455, + rw: 300, rh: 455, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 300, dh: 150, + rw: 300, rh: 150, + }); + + done(); + }); + wrapper.style.height = '150px'; + }); + wrapper.style.height = '455px'; + }); + + it('should not include parent padding when resizing the canvas', function(done) { + var chart = acquireChart({ + type: 'line', + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'padding: 50px; width: 320px; height: 350px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 350, + rw: 320, rh: 350, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 355, + rw: 455, rh: 355, + }); + + done(); + }); + wrapper.style.height = '355px'; + wrapper.style.width = '455px'; + }); + + it('should resize the canvas when the canvas display style changes from "none" to "block"', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: 'display: none;' + }, + wrapper: { + style: 'width: 320px; height: 350px' + } + }); + + var canvas = chart.canvas; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 320, dh: 350, + rw: 320, rh: 350, + }); + + done(); + }); + canvas.style.display = 'block'; + }); + + it('should resize the canvas when the wrapper display style changes from "none" to "block"', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'display: none; width: 460px; height: 380px' + } + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 460, dh: 380, + rw: 460, rh: 380, + }); + + done(); + }); + wrapper.style.display = 'block'; + }); + + // https://github.com/chartjs/Chart.js/issues/5485 + it('should resize the canvas when the devicePixelRatio changes', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false, + devicePixelRatio: 1 + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 400px; height: 200px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 400, dh: 200, + rw: 400, rh: 200, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 400, dh: 200, + rw: 800, rh: 400, + }); + + done(); + }); + chart.options.devicePixelRatio = 2; + chart.resize(); + }); + + // https://github.com/chartjs/Chart.js/issues/3790 + it('should resize the canvas if attached to the DOM after construction', function(done) { + var canvas = document.createElement('canvas'); + var wrapper = document.createElement('div'); + var body = window.document.body; + var chart = new Chart(canvas, { + type: 'line', + options: { + responsive: true, + maintainAspectRatio: false + } + }); + + expect(chart).toBeChartOfSize({ + dw: 0, dh: 0, + rw: 0, rh: 0, + }); + expect(chart.chartArea).toBeUndefined(); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 355, + rw: 455, rh: 355, + }); + + expect(chart.chartArea).not.toBeUndefined(); + + body.removeChild(wrapper); + chart.destroy(); + done(); + }); + + wrapper.style.cssText = 'width: 455px; height: 355px'; + wrapper.appendChild(canvas); + body.appendChild(wrapper); + }); + + it('should resize the canvas when attached to a different parent', function(done) { + var canvas = document.createElement('canvas'); + var wrapper = document.createElement('div'); + var body = window.document.body; + var chart = new Chart(canvas, { + type: 'line', + options: { + responsive: true, + maintainAspectRatio: false + } + }); + + expect(chart).toBeChartOfSize({ + dw: 0, dh: 0, + rw: 0, rh: 0, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 355, + rw: 455, rh: 355, + }); + + var target = document.createElement('div'); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 640, dh: 480, + rw: 640, rh: 480, + }); + + body.removeChild(wrapper); + body.removeChild(target); + chart.destroy(); + done(); + }); + + target.style.cssText = 'width: 640px; height: 480px'; + target.appendChild(canvas); + body.appendChild(target); + }); + + wrapper.style.cssText = 'width: 455px; height: 355px'; + wrapper.appendChild(canvas); + body.appendChild(wrapper); + }); + + // https://github.com/chartjs/Chart.js/issues/3521 + it('should resize the canvas after the wrapper has been re-attached to the DOM', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: false + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 320px; height: 350px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 350, + rw: 320, rh: 350, + }); + + var wrapper = chart.canvas.parentNode; + var parent = wrapper.parentNode; + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 320, dh: 355, + rw: 320, rh: 355, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 355, + rw: 455, rh: 355, + }); + + done(); + }); + + parent.removeChild(wrapper); + wrapper.style.width = '455px'; + parent.appendChild(wrapper); + }); + + parent.removeChild(wrapper); + parent.appendChild(wrapper); + wrapper.style.height = '355px'; + }); + + // https://github.com/chartjs/Chart.js/issues/4737 + it('should resize the canvas when re-creating the chart', function(done) { + var chart = acquireChart({ + options: { + responsive: true + } + }, { + wrapper: { + style: 'width: 320px' + } + }); + + var wrapper = chart.canvas.parentNode; + + waitForResize(chart, function() { + var canvas = chart.canvas; + expect(chart).toBeChartOfSize({ + dw: 320, dh: 320, + rw: 320, rh: 320, + }); + + chart.destroy(); + chart = new Chart(canvas, { + type: 'line', + options: { + responsive: true + } + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 455, + rw: 455, rh: 455, + }); + + chart.destroy(); + window.document.body.removeChild(wrapper); + done(); + }); + canvas.parentNode.style.width = '455px'; + }); + }); + }); + + describe('config.options.responsive: true (maintainAspectRatio: true)', function() { + it('should resize the canvas with correct aspect ratio when parent width changes', function(done) { + var chart = acquireChart({ + type: 'line', // AR == 2 + options: { + responsive: true, + maintainAspectRatio: true + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 300px; height: 350px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 150, + rw: 300, rh: 150, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 450, dh: 225, + rw: 450, rh: 225, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 150, dh: 75, + rw: 150, rh: 75, + }); + + done(); + }); + wrapper.style.width = '150px'; + }); + wrapper.style.width = '450px'; + }); + + it('should not resize the canvas when parent height changes', function(done) { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: true + } + }, { + canvas: { + style: '' + }, + wrapper: { + style: 'width: 320px; height: 350px; position: relative' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 160, + rw: 320, rh: 160, + }); + + var wrapper = chart.canvas.parentNode; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 320, dh: 160, + rw: 320, rh: 160, + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 320, dh: 160, + rw: 320, rh: 160, + }); + + done(); + }); + wrapper.style.height = '150px'; + }); + wrapper.style.height = '455px'; + }); + }); + + describe('Retina scale (a.k.a. device pixel ratio)', function() { + beforeEach(function() { + this.devicePixelRatio = window.devicePixelRatio; + window.devicePixelRatio = 3; + }); + + afterEach(function() { + window.devicePixelRatio = this.devicePixelRatio; + }); + + // see https://github.com/chartjs/Chart.js/issues/3575 + it ('should scale the render size but not the "implicit" display size', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + width: 320, + height: 240, + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 240, + rw: 960, rh: 720, + }); + }); + + it ('should scale the render size but not the "explicit" display size', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 320px; height: 240px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 240, + rw: 960, rh: 720, + }); + }); + }); + + describe('config.options.devicePixelRatio', function() { + beforeEach(function() { + this.devicePixelRatio = window.devicePixelRatio; + window.devicePixelRatio = 1; + }); + + afterEach(function() { + window.devicePixelRatio = this.devicePixelRatio; + }); + + // see https://github.com/chartjs/Chart.js/issues/3575 + it ('should scale the render size but not the "implicit" display size', function() { + var chart = acquireChart({ + options: { + responsive: false, + devicePixelRatio: 3 + } + }, { + canvas: { + width: 320, + height: 240, + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 240, + rw: 960, rh: 720, + }); + }); + + it ('should scale the render size but not the "explicit" display size', function() { + var chart = acquireChart({ + options: { + responsive: false, + devicePixelRatio: 3 + } + }, { + canvas: { + style: 'width: 320px; height: 240px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 320, dh: 240, + rw: 960, rh: 720, + }); + }); + }); + + describe('controller.reset', function() { + it('should reset the chart elements', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 0] + }] + }, + options: { + responsive: true + } + }); + + var meta = chart.getDatasetMeta(0); + + // Verify that points are at their initial correct location, + // then we will reset and see that they moved + expect(meta.data[0].y).toBeCloseToPixel(333); + expect(meta.data[1].y).toBeCloseToPixel(183); + expect(meta.data[2].y).toBeCloseToPixel(32); + expect(meta.data[3].y).toBeCloseToPixel(482); + + chart.reset(); + + // For a line chart, the animation state is the bottom + expect(meta.data[0].y).toBeCloseToPixel(482); + expect(meta.data[1].y).toBeCloseToPixel(482); + expect(meta.data[2].y).toBeCloseToPixel(482); + expect(meta.data[3].y).toBeCloseToPixel(482); + }); + }); + + describe('config update', function() { + it ('should update options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + chart.options = { + responsive: false, + scales: { + y: { + min: 0, + max: 10 + } + } + }; + chart.update(); + + var yScale = chart.scales.y; + expect(yScale.options.min).toBe(0); + expect(yScale.options.max).toBe(10); + }); + + it ('should update scales options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + chart.options.scales.y.min = 0; + chart.options.scales.y.max = 10; + chart.update(); + + var yScale = chart.scales.y; + expect(yScale.options.min).toBe(0); + expect(yScale.options.max).toBe(10); + }); + + it ('should update scales options from new object', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + var newScalesConfig = { + y: { + min: 0, + max: 10 + } + }; + chart.options.scales = newScalesConfig; + + chart.update(); + + var yScale = chart.scales.y; + expect(yScale.options.min).toBe(0); + expect(yScale.options.max).toBe(10); + }); + + it ('should remove discarded scale', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true, + scales: { + y: { + min: 0, + max: 10 + } + } + } + }); + + var newScalesConfig = { + y: { + min: 0, + max: 10 + } + }; + chart.options.scales = newScalesConfig; + + chart.update(); + + var yScale = chart.scales.yAxis0; + expect(yScale).toBeUndefined(); + var newyScale = chart.scales.y; + expect(newyScale.options.min).toBe(0); + expect(newyScale.options.max).toBe(10); + }); + + it ('should update tooltip options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + var newTooltipConfig = { + mode: 'dataset', + intersect: false + }; + chart.options.plugins.tooltip = newTooltipConfig; + + chart.update(); + expect(chart.tooltip.options).toEqual(jasmine.objectContaining(newTooltipConfig)); + }); + + it ('should update the tooltip on update', function(done) { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true, + tooltip: { + mode: 'nearest' + } + } + }); + + // Trigger an event over top of a point to + // put an item into the tooltip + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + + expect(chart._active[0].element).toEqual(point); + expect(tooltip._active[0].element).toEqual(point); + + // Update and confirm tooltip is updated + chart.update(); + expect(chart._active[0].element).toEqual(point); + expect(tooltip._active[0].element).toEqual(point); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it ('should update the metadata', function() { + var cfg = { + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + type: 'line', + data: [10, 20, 30, 0] + }] + }, + options: { + responsive: true, + scales: { + x: { + type: 'category' + }, + y: { + type: 'linear', + scaleLabel: { + display: true, + labelString: 'Value' + } + } + } + } + }; + var chart = acquireChart(cfg); + var meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('line'); + + // change the dataset to bar and check that meta was updated + chart.config.data.datasets[0].type = 'bar'; + chart.update(); + meta = chart.getDatasetMeta(0); + expect(meta.type).toBe('bar'); + }); + }); + + describe('plugin.extensions', function() { + var hooks = { + install: ['install'], + uninstall: ['uninstall'], + init: [ + 'beforeInit', + 'resize', + 'afterInit' + ], + start: ['start'], + stop: ['stop'], + update: [ + 'beforeUpdate', + 'beforeLayout', + 'beforeDataLimits', + 'afterDataLimits', + 'beforeBuildTicks', + 'afterBuildTicks', + 'beforeDataLimits', + 'afterDataLimits', + 'beforeBuildTicks', + 'afterBuildTicks', + 'afterLayout', + 'beforeDatasetsUpdate', + 'beforeDatasetUpdate', + 'afterDatasetUpdate', + 'afterDatasetsUpdate', + 'afterUpdate', + ], + render: [ + 'beforeRender', + 'beforeDraw', + 'beforeDatasetsDraw', + 'beforeDatasetDraw', + 'afterDatasetDraw', + 'afterDatasetsDraw', + 'beforeTooltipDraw', + 'afterTooltipDraw', + 'afterDraw', + 'afterRender', + ], + resize: [ + 'resize' + ], + destroy: [ + 'destroy' + ] + }; + + it ('should notify plugin in correct order', function(done) { + var plugin = this.plugin = {}; + var sequence = []; + + Object.keys(hooks).forEach(function(group) { + hooks[group].forEach(function(name) { + plugin[name] = function() { + sequence.push(name); + }; + }); + }); + + var chart = window.acquireChart({ + type: 'line', + data: {datasets: [{}]}, + plugins: [plugin], + options: { + responsive: true + } + }, { + wrapper: { + style: 'width: 300px' + } + }); + + waitForResize(chart, function() { + chart.destroy(); + + expect(sequence).toEqual([].concat( + hooks.install, + hooks.start, + hooks.init, + hooks.update, + hooks.render, + hooks.resize, + hooks.update, + hooks.render, + hooks.destroy, + hooks.stop, + hooks.uninstall + )); + + done(); + }); + chart.canvas.parentNode.style.width = '400px'; + }); + + it ('should notify initially disabled plugin in correct order', function() { + var plugin = this.plugin = {id: 'plugin'}; + var sequence = []; + + Object.keys(hooks).forEach(function(group) { + hooks[group].forEach(function(name) { + plugin[name] = function() { + sequence.push(name); + }; + }); + }); + + var chart = window.acquireChart({ + type: 'line', + data: {datasets: [{}]}, + plugins: [plugin], + options: { + plugins: { + plugin: false + } + } + }); + + expect(sequence).toEqual([].concat( + hooks.install + )); + + sequence = []; + chart.options.plugins.plugin = true; + chart.update(); + + expect(sequence).toEqual([].concat( + hooks.start, + hooks.update, + hooks.render + )); + + sequence = []; + chart.options.plugins.plugin = false; + chart.update(); + + expect(sequence).toEqual(hooks.stop); + + sequence = []; + chart.destroy(); + + expect(sequence).toEqual(hooks.uninstall); + }); + + it('should not notify before/afterDatasetDraw if dataset is hidden', function() { + var sequence = []; + var plugin = this.plugin = { + beforeDatasetDraw: function(chart, args) { + sequence.push('before-' + args.index); + }, + afterDatasetDraw: function(chart, args) { + sequence.push('after-' + args.index); + } + }; + + window.acquireChart({ + type: 'line', + data: {datasets: [{}, {hidden: true}, {}]}, + plugins: [plugin] + }); + + expect(sequence).toEqual([ + 'before-2', 'after-2', + 'before-0', 'after-0' + ]); + }); + }); + + describe('metasets', function() { + beforeEach(function() { + this.chart = acquireChart({ + type: 'line', + data: { + datasets: [ + {label: '1', order: 2}, + {label: '2', order: 1}, + {label: '3', order: 4}, + {label: '4', order: 3}, + ] + } + }); + }); + afterEach(function() { + const metasets = this.chart._metasets; + expect(metasets.length).toEqual(this.chart.data.datasets.length); + for (let i = 0; i < metasets.length; i++) { + expect(metasets[i].index).toEqual(i); + expect(metasets[i]._dataset).toEqual(this.chart.data.datasets[i]); + } + }); + it('should build metasets array in order', function() { + const metasets = this.chart._metasets; + expect(metasets[0].order).toEqual(2); + expect(metasets[1].order).toEqual(1); + expect(metasets[2].order).toEqual(4); + expect(metasets[3].order).toEqual(3); + }); + it('should build sorted metasets array in correct order', function() { + const metasets = this.chart._sortedMetasets; + expect(metasets[0].order).toEqual(1); + expect(metasets[1].order).toEqual(2); + expect(metasets[2].order).toEqual(3); + expect(metasets[3].order).toEqual(4); + }); + it('should be moved when datasets are removed from begining', function() { + this.chart.data.datasets.splice(0, 2); + this.chart.update(); + const metasets = this.chart._metasets; + expect(metasets[0].order).toEqual(4); + expect(metasets[1].order).toEqual(3); + }); + it('should be moved when datasets are removed from middle', function() { + this.chart.data.datasets.splice(1, 2); + this.chart.update(); + const metasets = this.chart._metasets; + expect(metasets[0].order).toEqual(2); + expect(metasets[1].order).toEqual(3); + }); + it('should be moved when datasets are inserted', function() { + this.chart.data.datasets.splice(1, 0, {label: '1.5', order: 5}); + this.chart.update(); + const metasets = this.chart._metasets; + expect(metasets[0].order).toEqual(2); + expect(metasets[1].order).toEqual(5); + expect(metasets[2].order).toEqual(1); + expect(metasets[3].order).toEqual(4); + expect(metasets[4].order).toEqual(3); + }); + it('should be replaced when dataset is replaced', function() { + this.chart.data.datasets.splice(1, 1, {label: '1.5', order: 5}); + this.chart.update(); + const metasets = this.chart._metasets; + expect(metasets[0].order).toEqual(2); + expect(metasets[1].order).toEqual(5); + expect(metasets[2].order).toEqual(4); + expect(metasets[3].order).toEqual(3); + }); + }); + + describe('data visibility', function() { + it('should hide a dataset', function() { + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 1, 2] + }], + labels: ['a', 'b', 'c'] + } + }); + + chart.setDatasetVisibility(0, false); + + var meta = chart.getDatasetMeta(0); + expect(meta.hidden).toBe(true); + }); + + it('should toggle data visibility by index', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3] + }] + } + }); + + expect(chart.getDataVisibility(1)).toBe(true); + + chart.toggleDataVisibility(1); + expect(chart.getDataVisibility(1)).toBe(false); + + chart.update(); + expect(chart.getDataVisibility(1)).toBe(false); + }); + }); + + describe('isDatasetVisible', function() { + it('should return false if index is out of bounds', function() { + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 1, 2] + }], + labels: ['a', 'b', 'c'] + } + }); + + expect(chart.isDatasetVisible(1)).toBe(false); + }); + }); + + describe('getChart', function() { + it('should get the chart from the canvas ID', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3] + }] + } + }); + chart.canvas.id = 'myID'; + + expect(Chart.getChart('myID')).toBe(chart); + }); + + it('should get the chart from an HTMLCanvasElement', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3] + }] + } + }); + expect(Chart.getChart(chart.canvas)).toBe(chart); + }); + + it('should get the chart from an CanvasRenderingContext2D', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3] + }] + } + }); + expect(Chart.getChart(chart.ctx)).toBe(chart); + }); + + it('should return undefined when a chart is not found or bad data is provided', function() { + expect(Chart.getChart(1)).toBeUndefined(); + }); + }); + + describe('active elements', function() { + it('should set the active elements', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3], + borderColor: 'red', + hoverBorderColor: 'blue', + }] + } + }); + + const meta = chart.getDatasetMeta(0); + let props = meta.data[0].getProps(['borderColor']); + expect(props.options.borderColor).toEqual('red'); + + chart.setActiveElements([{ + datasetIndex: 0, + index: 0, + }]); + + props = meta.data[0].getProps(['borderColor']); + expect(props.options.borderColor).toEqual('blue'); + + const active = chart.getActiveElements(); + expect(active.length).toEqual(1); + expect(active[0].element).toBe(meta.data[0]); + }); + }); }); diff --git a/test/specs/core.datasetController.tests.js b/test/specs/core.datasetController.tests.js index 473611e1a90..2b34a8fa36c 100644 --- a/test/specs/core.datasetController.tests.js +++ b/test/specs/core.datasetController.tests.js @@ -1,861 +1,861 @@ describe('Chart.DatasetController', function() { - it('should listen for dataset data insertions or removals', function() { - var data = [0, 1, 2, 3, 4, 5]; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - var controller = chart.getDatasetMeta(0).controller; - var methods = [ - '_onDataPush', - '_onDataPop', - '_onDataShift', - '_onDataSplice', - '_onDataUnshift' - ]; - - methods.forEach(function(method) { - spyOn(controller, method); - }); - - data.push(6, 7, 8); - data.push(9); - data.pop(); - data.shift(); - data.shift(); - data.shift(); - data.splice(1, 4, 10, 11); - data.unshift(12, 13, 14, 15); - data.unshift(16, 17); - - [2, 1, 3, 1, 2].forEach(function(expected, index) { - expect(controller[methods[index]].calls.count()).toBe(expected); - }); - }); - - it('should not try to delete non existent stacks', function() { - function createAndUpdateChart() { - var chart = acquireChart({ - data: { - labels: ['q'], - datasets: [ - { - id: 'dismissed', - label: 'Test before', - yAxisID: 'count', - data: [816], - type: 'bar', - stack: 'stack' - } - ] - }, - options: { - scales: { - count: { - axis: 'y', - type: 'linear' - } - } - } - }); - - chart.data = { - datasets: [ - { - id: 'tests', - yAxisID: 'count', - label: 'Test after', - data: [38300], - type: 'bar' - } - ], - labels: ['q'] - }; - - chart.update(); - } - - expect(createAndUpdateChart).not.toThrow(); - }); - - describe('inextensible data', function() { - it('should handle a frozen data object', function() { - function createChart() { - var data = Object.freeze([0, 1, 2, 3, 4, 5]); - expect(Object.isExtensible(data)).toBeFalsy(); - - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - var dataset = chart.data.datasets[0]; - dataset.data = Object.freeze([5, 4, 3, 2, 1, 0]); - expect(Object.isExtensible(dataset.data)).toBeFalsy(); - chart.update(); - - // Tests that the unlisten path also works for frozen objects - chart.destroy(); - } - - expect(createChart).not.toThrow(); - }); - - it('should handle a sealed data object', function() { - function createChart() { - var data = Object.seal([0, 1, 2, 3, 4, 5]); - expect(Object.isExtensible(data)).toBeFalsy(); - - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - var dataset = chart.data.datasets[0]; - dataset.data = Object.seal([5, 4, 3, 2, 1, 0]); - expect(Object.isExtensible(dataset.data)).toBeFalsy(); - chart.update(); - - // Tests that the unlisten path also works for frozen objects - chart.destroy(); - } - - expect(createChart).not.toThrow(); - }); - - it('should handle an unextendable data object', function() { - function createChart() { - var data = Object.preventExtensions([0, 1, 2, 3, 4, 5]); - expect(Object.isExtensible(data)).toBeFalsy(); - - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - var dataset = chart.data.datasets[0]; - dataset.data = Object.preventExtensions([5, 4, 3, 2, 1, 0]); - expect(Object.isExtensible(dataset.data)).toBeFalsy(); - chart.update(); - - // Tests that the unlisten path also works for frozen objects - chart.destroy(); - } - - expect(createChart).not.toThrow(); - }); - }); - - it('should parse data using correct scales', function() { - const data1 = [0, 1, 2, 3, 4, 5]; - const data2 = ['a', 'b', 'c', 'd', 'a']; - const chart = acquireChart({ - type: 'line', - data: { - datasets: [ - {data: data1}, - {data: data2, xAxisID: 'x2', yAxisID: 'y2'} - ] - }, - options: { - scales: { - x: { - type: 'category', - labels: ['one', 'two', 'three', 'four', 'five', 'six'] - }, - x2: { - type: 'logarithmic', - labels: ['1', '10', '100', '1000', '2000'] - }, - y: { - type: 'linear' - }, - y2: { - type: 'category', - labels: ['a', 'b', 'c', 'd', 'e'] - } - } - } - }); - - const meta1 = chart.getDatasetMeta(0); - const parsedXValues1 = meta1._parsed.map(p => p.x); - const parsedYValues1 = meta1._parsed.map(p => p.y); - - expect(meta1.data.length).toBe(6); - expect(parsedXValues1).toEqual([0, 1, 2, 3, 4, 5]); // label indices - expect(parsedYValues1).toEqual(data1); - - const meta2 = chart.getDatasetMeta(1); - const parsedXValues2 = meta2._parsed.map(p => p.x); - const parsedYValues2 = meta2._parsed.map(p => p.y); - - expect(meta2.data.length).toBe(5); - expect(parsedXValues2).toEqual([1, 10, 100, 1000, 2000]); // logarithmic scale labels - expect(parsedYValues2).toEqual([0, 1, 2, 3, 0]); // label indices - }); - - it('should parse using provided keys', function() { - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [ - {x: 1, data: {key: 'one', value: 20}}, - {data: {key: 'two', value: 30}} - ] - }] - }, - options: { - parsing: { - xAxisKey: 'data.key', - yAxisKey: 'data.value' - }, - scales: { - x: { - type: 'category', - labels: ['one', 'two'] - }, - y: { - type: 'linear' - }, - } - } - }); - - const meta = chart.getDatasetMeta(0); - const parsedXValues = meta._parsed.map(p => p.x); - const parsedYValues = meta._parsed.map(p => p.y); - - expect(meta.data.length).toBe(2); - expect(parsedXValues).toEqual([0, 1]); // label indices - expect(parsedYValues).toEqual([20, 30]); - }); - - it('should synchronize metadata when data are inserted or removed and parsing is on', function() { - const data = [0, 1, 2, 3, 4, 5]; - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - const meta = chart.getDatasetMeta(0); - const parsedYValues = () => meta._parsed.map(p => p.y); - let first, second, last; - - first = meta.data[0]; - last = meta.data[5]; - data.push(6, 7, 8); - data.push(9); - expect(meta.data.length).toBe(10); - expect(meta.data[0]).toBe(first); - expect(meta.data[5]).toBe(last); - expect(parsedYValues()).toEqual(data); - - last = meta.data[9]; - data.pop(); - expect(meta.data.length).toBe(9); - expect(meta.data[0]).toBe(first); - expect(meta.data.indexOf(last)).toBe(-1); - expect(parsedYValues()).toEqual(data); - - last = meta.data[8]; - data.shift(); - data.shift(); - data.shift(); - expect(meta.data.length).toBe(6); - expect(meta.data.indexOf(first)).toBe(-1); - expect(meta.data[5]).toBe(last); - expect(parsedYValues()).toEqual(data); - - first = meta.data[0]; - second = meta.data[1]; - last = meta.data[5]; - data.splice(1, 4, 10, 11); - expect(meta.data.length).toBe(4); - expect(meta.data[0]).toBe(first); - expect(meta.data[3]).toBe(last); - expect(meta.data.indexOf(second)).toBe(-1); - expect(parsedYValues()).toEqual(data); - - data.unshift(12, 13, 14, 15); - data.unshift(16, 17); - expect(meta.data.length).toBe(10); - expect(meta.data[6]).toBe(first); - expect(meta.data[9]).toBe(last); - expect(parsedYValues()).toEqual(data); - }); - - it('should synchronize metadata when data are inserted or removed and parsing is off', function() { - var data = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}]; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - }, - options: { - parsing: false, - scales: { - x: {type: 'linear'}, - y: {type: 'linear'} - } - } - }); - - var meta = chart.getDatasetMeta(0); - var controller = meta.controller; - var first, last; - - first = controller.getParsed(0); - last = controller.getParsed(5); - data.push({x: 6, y: 6}, {x: 7, y: 7}, {x: 8, y: 8}); - data.push({x: 9, y: 9}); - expect(meta.data.length).toBe(10); - expect(controller.getParsed(0)).toBe(first); - expect(controller.getParsed(5)).toBe(last); - - last = controller.getParsed(9); - data.pop(); - expect(meta.data.length).toBe(9); - expect(controller.getParsed(0)).toBe(first); - expect(controller.getParsed(9)).toBe(undefined); - expect(controller.getParsed(8)).toEqual({x: 8, y: 8}); - - last = controller.getParsed(8); - data.shift(); - data.shift(); - data.shift(); - expect(meta.data.length).toBe(6); - expect(controller.getParsed(5)).toBe(last); - - first = controller.getParsed(0); - last = controller.getParsed(5); - data.splice(1, 4, {x: 10, y: 10}, {x: 11, y: 11}); - expect(meta.data.length).toBe(4); - expect(controller.getParsed(0)).toBe(first); - expect(controller.getParsed(3)).toBe(last); - expect(controller.getParsed(1)).toEqual({x: 10, y: 10}); - - data.unshift({x: 12, y: 12}, {x: 13, y: 13}, {x: 14, y: 14}, {x: 15, y: 15}); - data.unshift({x: 16, y: 16}, {x: 17, y: 17}); - expect(meta.data.length).toBe(10); - expect(controller.getParsed(6)).toBe(first); - expect(controller.getParsed(9)).toBe(last); - }); - - it('should re-synchronize metadata when the data object reference changes', function() { - var data0 = [0, 1, 2, 3, 4, 5]; - var data1 = [6, 7, 8]; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data0 - }] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(6); - expect(meta._parsed.map(p => p.y)).toEqual(data0); - - chart.data.datasets[0].data = data1; - chart.update(); - - expect(meta.data.length).toBe(3); - expect(meta._parsed.map(p => p.y)).toEqual(data1); - - data1.push(9); - expect(meta.data.length).toBe(4); - - chart.data.datasets[0].data = data0; - chart.update(); - - expect(meta.data.length).toBe(6); - expect(meta._parsed.map(p => p.y)).toEqual(data0); - }); - - it('should re-synchronize metadata when data are unusually altered', function() { - var data = [0, 1, 2, 3, 4, 5]; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data - }] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(6); - - data.length = 2; - chart.update(); - - expect(meta.data.length).toBe(2); - - data.length = 42; - chart.update(); - - expect(meta.data.length).toBe(42); - }); - - // https://github.com/chartjs/Chart.js/issues/7243 - it('should re-synchronize metadata when data is moved and values are equal', function() { - var data = [10, 10, 10, 10, 10, 10]; - var chart = acquireChart({ - type: 'line', - data: { - labels: ['a', 'b', 'c', 'd', 'e', 'f'], - datasets: [{ - data, - fill: true - }] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(6); - const firstX = meta.data[0].x; - - data.push(data.shift()); - chart.update(); - - expect(meta.data.length).toBe(6); - expect(meta.data[0].x).toEqual(firstX); - }); - - // https://github.com/chartjs/Chart.js/issues/7445 - it('should re-synchronize metadata when data is objects and directly altered', function() { - var data = [{x: 'a', y: 1}, {x: 'b', y: 2}, {x: 'c', y: 3}]; - var chart = acquireChart({ - type: 'line', - data: { - labels: ['a', 'b', 'c'], - datasets: [{ - data, - fill: true - }] - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.data.length).toBe(3); - const y3 = meta.data[2].y; - - data[0].y = 3; - chart.update(); - expect(meta.data[0].y).toEqual(y3); - }); - - it('should re-synchronize metadata when scaleID changes', function() { - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [], - xAxisID: 'firstXScaleID', - yAxisID: 'firstYScaleID', - }] - }, - options: { - scales: { - firstXScaleID: { - type: 'category', - position: 'bottom' - }, - secondXScaleID: { - type: 'category', - position: 'bottom' - }, - firstYScaleID: { - type: 'linear', - position: 'left' - }, - secondYScaleID: { - type: 'linear', - position: 'left' - }, - } - } - }); - - var meta = chart.getDatasetMeta(0); - - expect(meta.xAxisID).toBe('firstXScaleID'); - expect(meta.yAxisID).toBe('firstYScaleID'); - - chart.data.datasets[0].xAxisID = 'secondXScaleID'; - chart.data.datasets[0].yAxisID = 'secondYScaleID'; - chart.update(); - - expect(meta.xAxisID).toBe('secondXScaleID'); - expect(meta.yAxisID).toBe('secondYScaleID'); - }); - - it('should re-synchronize stacks when stack is changed', function() { - var chart = acquireChart({ - type: 'bar', - data: { - labels: ['a', 'b'], - datasets: [{ - data: [1, 10], - stack: '1' - }, { - data: [2, 20], - stack: '2' - }, { - data: [3, 30], - stack: '1' - }] - } - }); - - expect(chart._stacks).toEqual({ - 'x.y.1.bar': { - 0: {0: 1, 2: 3}, - 1: {0: 10, 2: 30} - }, - 'x.y.2.bar': { - 0: {1: 2}, - 1: {1: 20} - } - }); - - chart.data.datasets[2].stack = '2'; - chart.update(); - - expect(chart._stacks).toEqual({ - 'x.y.1.bar': { - 0: {0: 1}, - 1: {0: 10} - }, - 'x.y.2.bar': { - 0: {1: 2, 2: 3}, - 1: {1: 20, 2: 30} - } - }); - }); - - it('should re-synchronize stacks when data is removed', function() { - var chart = acquireChart({ - type: 'bar', - data: { - labels: ['a', 'b'], - datasets: [{ - data: [1, 10], - stack: '1' - }, { - data: [2, 20], - stack: '2' - }, { - data: [3, 30], - stack: '1' - }] - } - }); - - expect(chart._stacks).toEqual({ - 'x.y.1.bar': { - 0: {0: 1, 2: 3}, - 1: {0: 10, 2: 30} - }, - 'x.y.2.bar': { - 0: {1: 2}, - 1: {1: 20} - } - }); - - chart.data.datasets[2].data = [4]; - chart.update(); - - expect(chart._stacks).toEqual({ - 'x.y.1.bar': { - 0: {0: 1, 2: 4}, - 1: {0: 10} - }, - 'x.y.2.bar': { - 0: {1: 2}, - 1: {1: 20} - } - }); - }); - - it('should cleanup attached properties when the reference changes or when the chart is destroyed', function() { - var data0 = [0, 1, 2, 3, 4, 5]; - var data1 = [6, 7, 8]; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data0 - }] - } - }); - - var hooks = ['push', 'pop', 'shift', 'splice', 'unshift']; - - expect(data0._chartjs).toBeDefined(); - hooks.forEach(function(hook) { - expect(data0[hook]).not.toBe(Array.prototype[hook]); - }); - - expect(data1._chartjs).not.toBeDefined(); - hooks.forEach(function(hook) { - expect(data1[hook]).toBe(Array.prototype[hook]); - }); - - chart.data.datasets[0].data = data1; - chart.update(); - - expect(data0._chartjs).not.toBeDefined(); - hooks.forEach(function(hook) { - expect(data0[hook]).toBe(Array.prototype[hook]); - }); - - expect(data1._chartjs).toBeDefined(); - hooks.forEach(function(hook) { - expect(data1[hook]).not.toBe(Array.prototype[hook]); - }); - - chart.destroy(); - - expect(data1._chartjs).not.toBeDefined(); - hooks.forEach(function(hook) { - expect(data1[hook]).toBe(Array.prototype[hook]); - }); - }); - - it('should resolve data element options to the default color', function() { - var data0 = [0, 1, 2, 3, 4, 5]; - var oldColor = Chart.defaults.borderColor; - Chart.defaults.borderColor = 'red'; - var chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: data0 - }] - } - }); - - var meta = chart.getDatasetMeta(0); - expect(meta.dataset.options.borderColor).toBe('red'); - expect(meta.data[0].options.borderColor).toBe('red'); - - // Reset old shared state - Chart.defaults.borderColor = oldColor; - }); - - describe('_resolveOptions', function() { - it('should resove names in array notation', function() { - Chart.defaults.elements.line.globalTest = 'global'; - - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1], - datasetTest: 'dataset' - }] - }, - options: { - elements: { - line: { - elementTest: 'element' - } - } - } - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller._resolveOptions( - [ - 'datasetTest', - 'elementTest', - 'globalTest' - ], - {type: 'line'}) - ).toEqual({ - datasetTest: 'dataset', - elementTest: 'element', - globalTest: 'global' - }); - - // Remove test from global defaults - delete Chart.defaults.elements.line.globalTest; - }); - - it('should resove names in object notation', function() { - Chart.defaults.elements.line.global = 'global'; - - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1], - datasetTest: 'dataset' - }] - }, - options: { - elements: { - line: { - element: 'element' - } - } - } - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller._resolveOptions( - { - dataset: 'datasetTest', - element: 'elementTest', - global: 'globalTest'}, - {type: 'line'}) - ).toEqual({ - dataset: 'dataset', - element: 'element', - global: 'global' - }); - - // Remove test from global defaults - delete Chart.defaults.elements.line.global; - }); - }); - - describe('resolveDataElementOptions', function() { - it('should cache options when possible', function() { - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3], - }] - }, - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller.enableOptionSharing).toBeTrue(); - - const opts0 = controller.resolveDataElementOptions(0); - const opts1 = controller.resolveDataElementOptions(1); - - expect(opts0 === opts1).toBeTrue(); - expect(opts0.$shared).toBeTrue(); - expect(Object.isFrozen(opts0)).toBeTrue(); - }); - - it('should not cache options when option sharing is disabled', function() { - const chart = acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [1, 2, 3], - }] - }, - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller.enableOptionSharing).toBeFalse(); - - const opts0 = controller.resolveDataElementOptions(0); - const opts1 = controller.resolveDataElementOptions(1); - - expect(opts0 === opts1).toBeFalse(); - expect(opts0.$shared).not.toBeTrue(); - expect(Object.isFrozen(opts0)).toBeFalse(); - }); - - it('should not cache options when functions are used', function() { - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1, 2, 3], - backgroundColor: () => 'red' - }] - }, - }); - - const controller = chart.getDatasetMeta(0).controller; - - const opts0 = controller.resolveDataElementOptions(0); - const opts1 = controller.resolveDataElementOptions(1); - - expect(opts0 === opts1).toBeFalse(); - expect(opts0.$shared).not.toBeTrue(); - expect(Object.isFrozen(opts0)).toBeFalse(); - }); - }); - - describe('_resolveAnimations', function() { - it('should resolve to empty Animations when globally disabled', function() { - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1], - animation: { - test: {duration: 10} - } - }] - }, - options: { - animation: false - } - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller._resolveAnimations(0)._properties.size).toEqual(0); - }); - - it('should resolve to empty Animations when disabled at dataset level', function() { - const chart = acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [1], - animation: false - }] - } - }); - - const controller = chart.getDatasetMeta(0).controller; - - expect(controller._resolveAnimations(0)._properties.size).toEqual(0); - }); - }); + it('should listen for dataset data insertions or removals', function() { + var data = [0, 1, 2, 3, 4, 5]; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + var controller = chart.getDatasetMeta(0).controller; + var methods = [ + '_onDataPush', + '_onDataPop', + '_onDataShift', + '_onDataSplice', + '_onDataUnshift' + ]; + + methods.forEach(function(method) { + spyOn(controller, method); + }); + + data.push(6, 7, 8); + data.push(9); + data.pop(); + data.shift(); + data.shift(); + data.shift(); + data.splice(1, 4, 10, 11); + data.unshift(12, 13, 14, 15); + data.unshift(16, 17); + + [2, 1, 3, 1, 2].forEach(function(expected, index) { + expect(controller[methods[index]].calls.count()).toBe(expected); + }); + }); + + it('should not try to delete non existent stacks', function() { + function createAndUpdateChart() { + var chart = acquireChart({ + data: { + labels: ['q'], + datasets: [ + { + id: 'dismissed', + label: 'Test before', + yAxisID: 'count', + data: [816], + type: 'bar', + stack: 'stack' + } + ] + }, + options: { + scales: { + count: { + axis: 'y', + type: 'linear' + } + } + } + }); + + chart.data = { + datasets: [ + { + id: 'tests', + yAxisID: 'count', + label: 'Test after', + data: [38300], + type: 'bar' + } + ], + labels: ['q'] + }; + + chart.update(); + } + + expect(createAndUpdateChart).not.toThrow(); + }); + + describe('inextensible data', function() { + it('should handle a frozen data object', function() { + function createChart() { + var data = Object.freeze([0, 1, 2, 3, 4, 5]); + expect(Object.isExtensible(data)).toBeFalsy(); + + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + var dataset = chart.data.datasets[0]; + dataset.data = Object.freeze([5, 4, 3, 2, 1, 0]); + expect(Object.isExtensible(dataset.data)).toBeFalsy(); + chart.update(); + + // Tests that the unlisten path also works for frozen objects + chart.destroy(); + } + + expect(createChart).not.toThrow(); + }); + + it('should handle a sealed data object', function() { + function createChart() { + var data = Object.seal([0, 1, 2, 3, 4, 5]); + expect(Object.isExtensible(data)).toBeFalsy(); + + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + var dataset = chart.data.datasets[0]; + dataset.data = Object.seal([5, 4, 3, 2, 1, 0]); + expect(Object.isExtensible(dataset.data)).toBeFalsy(); + chart.update(); + + // Tests that the unlisten path also works for frozen objects + chart.destroy(); + } + + expect(createChart).not.toThrow(); + }); + + it('should handle an unextendable data object', function() { + function createChart() { + var data = Object.preventExtensions([0, 1, 2, 3, 4, 5]); + expect(Object.isExtensible(data)).toBeFalsy(); + + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + var dataset = chart.data.datasets[0]; + dataset.data = Object.preventExtensions([5, 4, 3, 2, 1, 0]); + expect(Object.isExtensible(dataset.data)).toBeFalsy(); + chart.update(); + + // Tests that the unlisten path also works for frozen objects + chart.destroy(); + } + + expect(createChart).not.toThrow(); + }); + }); + + it('should parse data using correct scales', function() { + const data1 = [0, 1, 2, 3, 4, 5]; + const data2 = ['a', 'b', 'c', 'd', 'a']; + const chart = acquireChart({ + type: 'line', + data: { + datasets: [ + {data: data1}, + {data: data2, xAxisID: 'x2', yAxisID: 'y2'} + ] + }, + options: { + scales: { + x: { + type: 'category', + labels: ['one', 'two', 'three', 'four', 'five', 'six'] + }, + x2: { + type: 'logarithmic', + labels: ['1', '10', '100', '1000', '2000'] + }, + y: { + type: 'linear' + }, + y2: { + type: 'category', + labels: ['a', 'b', 'c', 'd', 'e'] + } + } + } + }); + + const meta1 = chart.getDatasetMeta(0); + const parsedXValues1 = meta1._parsed.map(p => p.x); + const parsedYValues1 = meta1._parsed.map(p => p.y); + + expect(meta1.data.length).toBe(6); + expect(parsedXValues1).toEqual([0, 1, 2, 3, 4, 5]); // label indices + expect(parsedYValues1).toEqual(data1); + + const meta2 = chart.getDatasetMeta(1); + const parsedXValues2 = meta2._parsed.map(p => p.x); + const parsedYValues2 = meta2._parsed.map(p => p.y); + + expect(meta2.data.length).toBe(5); + expect(parsedXValues2).toEqual([1, 10, 100, 1000, 2000]); // logarithmic scale labels + expect(parsedYValues2).toEqual([0, 1, 2, 3, 0]); // label indices + }); + + it('should parse using provided keys', function() { + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [ + {x: 1, data: {key: 'one', value: 20}}, + {data: {key: 'two', value: 30}} + ] + }] + }, + options: { + parsing: { + xAxisKey: 'data.key', + yAxisKey: 'data.value' + }, + scales: { + x: { + type: 'category', + labels: ['one', 'two'] + }, + y: { + type: 'linear' + }, + } + } + }); + + const meta = chart.getDatasetMeta(0); + const parsedXValues = meta._parsed.map(p => p.x); + const parsedYValues = meta._parsed.map(p => p.y); + + expect(meta.data.length).toBe(2); + expect(parsedXValues).toEqual([0, 1]); // label indices + expect(parsedYValues).toEqual([20, 30]); + }); + + it('should synchronize metadata when data are inserted or removed and parsing is on', function() { + const data = [0, 1, 2, 3, 4, 5]; + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + const meta = chart.getDatasetMeta(0); + const parsedYValues = () => meta._parsed.map(p => p.y); + let first, second, last; + + first = meta.data[0]; + last = meta.data[5]; + data.push(6, 7, 8); + data.push(9); + expect(meta.data.length).toBe(10); + expect(meta.data[0]).toBe(first); + expect(meta.data[5]).toBe(last); + expect(parsedYValues()).toEqual(data); + + last = meta.data[9]; + data.pop(); + expect(meta.data.length).toBe(9); + expect(meta.data[0]).toBe(first); + expect(meta.data.indexOf(last)).toBe(-1); + expect(parsedYValues()).toEqual(data); + + last = meta.data[8]; + data.shift(); + data.shift(); + data.shift(); + expect(meta.data.length).toBe(6); + expect(meta.data.indexOf(first)).toBe(-1); + expect(meta.data[5]).toBe(last); + expect(parsedYValues()).toEqual(data); + + first = meta.data[0]; + second = meta.data[1]; + last = meta.data[5]; + data.splice(1, 4, 10, 11); + expect(meta.data.length).toBe(4); + expect(meta.data[0]).toBe(first); + expect(meta.data[3]).toBe(last); + expect(meta.data.indexOf(second)).toBe(-1); + expect(parsedYValues()).toEqual(data); + + data.unshift(12, 13, 14, 15); + data.unshift(16, 17); + expect(meta.data.length).toBe(10); + expect(meta.data[6]).toBe(first); + expect(meta.data[9]).toBe(last); + expect(parsedYValues()).toEqual(data); + }); + + it('should synchronize metadata when data are inserted or removed and parsing is off', function() { + var data = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}]; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + }, + options: { + parsing: false, + scales: { + x: {type: 'linear'}, + y: {type: 'linear'} + } + } + }); + + var meta = chart.getDatasetMeta(0); + var controller = meta.controller; + var first, last; + + first = controller.getParsed(0); + last = controller.getParsed(5); + data.push({x: 6, y: 6}, {x: 7, y: 7}, {x: 8, y: 8}); + data.push({x: 9, y: 9}); + expect(meta.data.length).toBe(10); + expect(controller.getParsed(0)).toBe(first); + expect(controller.getParsed(5)).toBe(last); + + last = controller.getParsed(9); + data.pop(); + expect(meta.data.length).toBe(9); + expect(controller.getParsed(0)).toBe(first); + expect(controller.getParsed(9)).toBe(undefined); + expect(controller.getParsed(8)).toEqual({x: 8, y: 8}); + + last = controller.getParsed(8); + data.shift(); + data.shift(); + data.shift(); + expect(meta.data.length).toBe(6); + expect(controller.getParsed(5)).toBe(last); + + first = controller.getParsed(0); + last = controller.getParsed(5); + data.splice(1, 4, {x: 10, y: 10}, {x: 11, y: 11}); + expect(meta.data.length).toBe(4); + expect(controller.getParsed(0)).toBe(first); + expect(controller.getParsed(3)).toBe(last); + expect(controller.getParsed(1)).toEqual({x: 10, y: 10}); + + data.unshift({x: 12, y: 12}, {x: 13, y: 13}, {x: 14, y: 14}, {x: 15, y: 15}); + data.unshift({x: 16, y: 16}, {x: 17, y: 17}); + expect(meta.data.length).toBe(10); + expect(controller.getParsed(6)).toBe(first); + expect(controller.getParsed(9)).toBe(last); + }); + + it('should re-synchronize metadata when the data object reference changes', function() { + var data0 = [0, 1, 2, 3, 4, 5]; + var data1 = [6, 7, 8]; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data0 + }] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(6); + expect(meta._parsed.map(p => p.y)).toEqual(data0); + + chart.data.datasets[0].data = data1; + chart.update(); + + expect(meta.data.length).toBe(3); + expect(meta._parsed.map(p => p.y)).toEqual(data1); + + data1.push(9); + expect(meta.data.length).toBe(4); + + chart.data.datasets[0].data = data0; + chart.update(); + + expect(meta.data.length).toBe(6); + expect(meta._parsed.map(p => p.y)).toEqual(data0); + }); + + it('should re-synchronize metadata when data are unusually altered', function() { + var data = [0, 1, 2, 3, 4, 5]; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data + }] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(6); + + data.length = 2; + chart.update(); + + expect(meta.data.length).toBe(2); + + data.length = 42; + chart.update(); + + expect(meta.data.length).toBe(42); + }); + + // https://github.com/chartjs/Chart.js/issues/7243 + it('should re-synchronize metadata when data is moved and values are equal', function() { + var data = [10, 10, 10, 10, 10, 10]; + var chart = acquireChart({ + type: 'line', + data: { + labels: ['a', 'b', 'c', 'd', 'e', 'f'], + datasets: [{ + data, + fill: true + }] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(6); + const firstX = meta.data[0].x; + + data.push(data.shift()); + chart.update(); + + expect(meta.data.length).toBe(6); + expect(meta.data[0].x).toEqual(firstX); + }); + + // https://github.com/chartjs/Chart.js/issues/7445 + it('should re-synchronize metadata when data is objects and directly altered', function() { + var data = [{x: 'a', y: 1}, {x: 'b', y: 2}, {x: 'c', y: 3}]; + var chart = acquireChart({ + type: 'line', + data: { + labels: ['a', 'b', 'c'], + datasets: [{ + data, + fill: true + }] + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.data.length).toBe(3); + const y3 = meta.data[2].y; + + data[0].y = 3; + chart.update(); + expect(meta.data[0].y).toEqual(y3); + }); + + it('should re-synchronize metadata when scaleID changes', function() { + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [], + xAxisID: 'firstXScaleID', + yAxisID: 'firstYScaleID', + }] + }, + options: { + scales: { + firstXScaleID: { + type: 'category', + position: 'bottom' + }, + secondXScaleID: { + type: 'category', + position: 'bottom' + }, + firstYScaleID: { + type: 'linear', + position: 'left' + }, + secondYScaleID: { + type: 'linear', + position: 'left' + }, + } + } + }); + + var meta = chart.getDatasetMeta(0); + + expect(meta.xAxisID).toBe('firstXScaleID'); + expect(meta.yAxisID).toBe('firstYScaleID'); + + chart.data.datasets[0].xAxisID = 'secondXScaleID'; + chart.data.datasets[0].yAxisID = 'secondYScaleID'; + chart.update(); + + expect(meta.xAxisID).toBe('secondXScaleID'); + expect(meta.yAxisID).toBe('secondYScaleID'); + }); + + it('should re-synchronize stacks when stack is changed', function() { + var chart = acquireChart({ + type: 'bar', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [1, 10], + stack: '1' + }, { + data: [2, 20], + stack: '2' + }, { + data: [3, 30], + stack: '1' + }] + } + }); + + expect(chart._stacks).toEqual({ + 'x.y.1.bar': { + 0: {0: 1, 2: 3}, + 1: {0: 10, 2: 30} + }, + 'x.y.2.bar': { + 0: {1: 2}, + 1: {1: 20} + } + }); + + chart.data.datasets[2].stack = '2'; + chart.update(); + + expect(chart._stacks).toEqual({ + 'x.y.1.bar': { + 0: {0: 1}, + 1: {0: 10} + }, + 'x.y.2.bar': { + 0: {1: 2, 2: 3}, + 1: {1: 20, 2: 30} + } + }); + }); + + it('should re-synchronize stacks when data is removed', function() { + var chart = acquireChart({ + type: 'bar', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [1, 10], + stack: '1' + }, { + data: [2, 20], + stack: '2' + }, { + data: [3, 30], + stack: '1' + }] + } + }); + + expect(chart._stacks).toEqual({ + 'x.y.1.bar': { + 0: {0: 1, 2: 3}, + 1: {0: 10, 2: 30} + }, + 'x.y.2.bar': { + 0: {1: 2}, + 1: {1: 20} + } + }); + + chart.data.datasets[2].data = [4]; + chart.update(); + + expect(chart._stacks).toEqual({ + 'x.y.1.bar': { + 0: {0: 1, 2: 4}, + 1: {0: 10} + }, + 'x.y.2.bar': { + 0: {1: 2}, + 1: {1: 20} + } + }); + }); + + it('should cleanup attached properties when the reference changes or when the chart is destroyed', function() { + var data0 = [0, 1, 2, 3, 4, 5]; + var data1 = [6, 7, 8]; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data0 + }] + } + }); + + var hooks = ['push', 'pop', 'shift', 'splice', 'unshift']; + + expect(data0._chartjs).toBeDefined(); + hooks.forEach(function(hook) { + expect(data0[hook]).not.toBe(Array.prototype[hook]); + }); + + expect(data1._chartjs).not.toBeDefined(); + hooks.forEach(function(hook) { + expect(data1[hook]).toBe(Array.prototype[hook]); + }); + + chart.data.datasets[0].data = data1; + chart.update(); + + expect(data0._chartjs).not.toBeDefined(); + hooks.forEach(function(hook) { + expect(data0[hook]).toBe(Array.prototype[hook]); + }); + + expect(data1._chartjs).toBeDefined(); + hooks.forEach(function(hook) { + expect(data1[hook]).not.toBe(Array.prototype[hook]); + }); + + chart.destroy(); + + expect(data1._chartjs).not.toBeDefined(); + hooks.forEach(function(hook) { + expect(data1[hook]).toBe(Array.prototype[hook]); + }); + }); + + it('should resolve data element options to the default color', function() { + var data0 = [0, 1, 2, 3, 4, 5]; + var oldColor = Chart.defaults.borderColor; + Chart.defaults.borderColor = 'red'; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data0 + }] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.dataset.options.borderColor).toBe('red'); + expect(meta.data[0].options.borderColor).toBe('red'); + + // Reset old shared state + Chart.defaults.borderColor = oldColor; + }); + + describe('_resolveOptions', function() { + it('should resove names in array notation', function() { + Chart.defaults.elements.line.globalTest = 'global'; + + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1], + datasetTest: 'dataset' + }] + }, + options: { + elements: { + line: { + elementTest: 'element' + } + } + } + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller._resolveOptions( + [ + 'datasetTest', + 'elementTest', + 'globalTest' + ], + {type: 'line'}) + ).toEqual({ + datasetTest: 'dataset', + elementTest: 'element', + globalTest: 'global' + }); + + // Remove test from global defaults + delete Chart.defaults.elements.line.globalTest; + }); + + it('should resove names in object notation', function() { + Chart.defaults.elements.line.global = 'global'; + + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1], + datasetTest: 'dataset' + }] + }, + options: { + elements: { + line: { + element: 'element' + } + } + } + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller._resolveOptions( + { + dataset: 'datasetTest', + element: 'elementTest', + global: 'globalTest'}, + {type: 'line'}) + ).toEqual({ + dataset: 'dataset', + element: 'element', + global: 'global' + }); + + // Remove test from global defaults + delete Chart.defaults.elements.line.global; + }); + }); + + describe('resolveDataElementOptions', function() { + it('should cache options when possible', function() { + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }] + }, + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller.enableOptionSharing).toBeTrue(); + + const opts0 = controller.resolveDataElementOptions(0); + const opts1 = controller.resolveDataElementOptions(1); + + expect(opts0 === opts1).toBeTrue(); + expect(opts0.$shared).toBeTrue(); + expect(Object.isFrozen(opts0)).toBeTrue(); + }); + + it('should not cache options when option sharing is disabled', function() { + const chart = acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [1, 2, 3], + }] + }, + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller.enableOptionSharing).toBeFalse(); + + const opts0 = controller.resolveDataElementOptions(0); + const opts1 = controller.resolveDataElementOptions(1); + + expect(opts0 === opts1).toBeFalse(); + expect(opts0.$shared).not.toBeTrue(); + expect(Object.isFrozen(opts0)).toBeFalse(); + }); + + it('should not cache options when functions are used', function() { + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + backgroundColor: () => 'red' + }] + }, + }); + + const controller = chart.getDatasetMeta(0).controller; + + const opts0 = controller.resolveDataElementOptions(0); + const opts1 = controller.resolveDataElementOptions(1); + + expect(opts0 === opts1).toBeFalse(); + expect(opts0.$shared).not.toBeTrue(); + expect(Object.isFrozen(opts0)).toBeFalse(); + }); + }); + + describe('_resolveAnimations', function() { + it('should resolve to empty Animations when globally disabled', function() { + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1], + animation: { + test: {duration: 10} + } + }] + }, + options: { + animation: false + } + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller._resolveAnimations(0)._properties.size).toEqual(0); + }); + + it('should resolve to empty Animations when disabled at dataset level', function() { + const chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [1], + animation: false + }] + } + }); + + const controller = chart.getDatasetMeta(0).controller; + + expect(controller._resolveAnimations(0)._properties.size).toEqual(0); + }); + }); }); diff --git a/test/specs/core.defaults.tests.js b/test/specs/core.defaults.tests.js index 9c1a7152814..5b22439bc61 100644 --- a/test/specs/core.defaults.tests.js +++ b/test/specs/core.defaults.tests.js @@ -1,38 +1,38 @@ describe('Chart.defaults', function() { - describe('.set', function() { - it('Should set defaults directly to root when scope is not provided', function() { - expect(Chart.defaults.test).toBeUndefined(); - Chart.defaults.set({test: true}); - expect(Chart.defaults.test).toEqual(true); - delete Chart.defaults.test; - }); + describe('.set', function() { + it('Should set defaults directly to root when scope is not provided', function() { + expect(Chart.defaults.test).toBeUndefined(); + Chart.defaults.set({test: true}); + expect(Chart.defaults.test).toEqual(true); + delete Chart.defaults.test; + }); - it('Should create scope when it does not exist', function() { - expect(Chart.defaults.test).toBeUndefined(); - Chart.defaults.set('test', {value: true}); - expect(Chart.defaults.test.value).toEqual(true); - delete Chart.defaults.test; - }); - }); + it('Should create scope when it does not exist', function() { + expect(Chart.defaults.test).toBeUndefined(); + Chart.defaults.set('test', {value: true}); + expect(Chart.defaults.test.value).toEqual(true); + delete Chart.defaults.test; + }); + }); - describe('.route', function() { - it('Should read the source, but not change it', function() { - expect(Chart.defaults.testscope).toBeUndefined(); + describe('.route', function() { + it('Should read the source, but not change it', function() { + expect(Chart.defaults.testscope).toBeUndefined(); - Chart.defaults.set('testscope', {test: true}); - Chart.defaults.route('testscope', 'test2', 'testscope', 'test'); + Chart.defaults.set('testscope', {test: true}); + Chart.defaults.route('testscope', 'test2', 'testscope', 'test'); - expect(Chart.defaults.testscope.test).toEqual(true); - expect(Chart.defaults.testscope.test2).toEqual(true); + expect(Chart.defaults.testscope.test).toEqual(true); + expect(Chart.defaults.testscope.test2).toEqual(true); - Chart.defaults.set('testscope', {test2: false}); - expect(Chart.defaults.testscope.test).toEqual(true); - expect(Chart.defaults.testscope.test2).toEqual(false); + Chart.defaults.set('testscope', {test2: false}); + expect(Chart.defaults.testscope.test).toEqual(true); + expect(Chart.defaults.testscope.test2).toEqual(false); - Chart.defaults.set('testscope', {test2: undefined}); - expect(Chart.defaults.testscope.test2).toEqual(true); + Chart.defaults.set('testscope', {test2: undefined}); + expect(Chart.defaults.testscope.test2).toEqual(true); - delete Chart.defaults.testscope; - }); - }); + delete Chart.defaults.testscope; + }); + }); }); diff --git a/test/specs/core.element.tests.js b/test/specs/core.element.tests.js index e3f0f1aba55..07e8adbf073 100644 --- a/test/specs/core.element.tests.js +++ b/test/specs/core.element.tests.js @@ -1,16 +1,16 @@ describe('Chart.element', function() { - describe('getProps', function() { - it('should return requested properties', function() { - const elem = new Chart.Element(); - elem.x = 10; - elem.y = 1.5; + describe('getProps', function() { + it('should return requested properties', function() { + const elem = new Chart.Element(); + elem.x = 10; + elem.y = 1.5; - expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); - expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); + expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); + expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); - elem.$animations = {x: {active: () => true, _to: 20}}; - expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); - expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 20, y: 1.5})); - }); - }); + elem.$animations = {x: {active: () => true, _to: 20}}; + expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5})); + expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 20, y: 1.5})); + }); + }); }); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index d48a3a06184..30dcb98bff6 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -1,24 +1,24 @@ describe('Core helper tests', function() { - var helpers; + var helpers; - beforeAll(function() { - helpers = window.Chart.helpers; - }); + beforeAll(function() { + helpers = window.Chart.helpers; + }); - it('should generate integer ids', function() { - var uid = helpers.uid(); - expect(uid).toEqual(jasmine.any(Number)); - expect(helpers.uid()).toBe(uid + 1); - expect(helpers.uid()).toBe(uid + 2); - expect(helpers.uid()).toBe(uid + 3); - }); + it('should generate integer ids', function() { + var uid = helpers.uid(); + expect(uid).toEqual(jasmine.any(Number)); + expect(helpers.uid()).toBe(uid + 1); + expect(helpers.uid()).toBe(uid + 2); + expect(helpers.uid()).toBe(uid + 3); + }); - describe('clone', function() { - it('should not allow prototype pollution', function() { - const test = helpers.clone(JSON.parse('{"__proto__":{"polluted": true}}')); - expect(test.prototype).toBeUndefined(); - expect(Object.prototype.polluted).toBeUndefined(); - }); - }); + describe('clone', function() { + it('should not allow prototype pollution', function() { + const test = helpers.clone(JSON.parse('{"__proto__":{"polluted": true}}')); + expect(test.prototype).toBeUndefined(); + expect(Object.prototype.polluted).toBeUndefined(); + }); + }); }); diff --git a/test/specs/core.interaction.tests.js b/test/specs/core.interaction.tests.js index 0f3504fb201..a3ce810db7f 100644 --- a/test/specs/core.interaction.tests.js +++ b/test/specs/core.interaction.tests.js @@ -2,787 +2,787 @@ // Test the rectangle element describe('Core.Interaction', function() { - describe('point mode', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 20, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - it ('should return all items under the point', function() { - var chart = this.chart; - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - var point = meta0.data[1]; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: point.x, - y: point.y, - }; - - var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element); - expect(elements).toEqual([point, meta1.data[1]]); - }); - - it ('should return an empty array when no items are found', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: 0, - y: 0 - }; - - var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element); - expect(elements).toEqual([]); - }); - }); - - describe('index mode', function() { - describe('intersect: true', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - it ('gets correct items', function() { - var chart = this.chart; - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - var point = meta0.data[1]; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: point.x, - y: point.y, - }; - - var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([point, meta1.data[1]]); - }); - - it ('returns empty array when nothing found', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: 0, - y: 0, - }; - - var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([]); - }); - }); - - describe ('intersect: false', function() { - var data = { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }; - - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: data - }); - }); - - it ('axis: x gets correct items', function() { - var chart = this.chart; - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: chart.chartArea.left, - y: chart.chartArea.top - }; - - var elements = Chart.Interaction.modes.index(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual([meta0.data[0], meta1.data[0]]); - }); - - it ('axis: y gets correct items', function() { - var chart = window.acquireChart({ - type: 'bar', - data: data, - options: { - indexAxis: 'y', - } - }); - - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - var center = meta0.data[0].getCenterPoint(); - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: center.x, - y: center.y + 30, - }; - - var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'y', intersect: false}).map(item => item.element); - expect(elements).toEqual([meta0.data[0], meta1.data[0]]); - }); - - it ('axis: xy gets correct items', function() { - var chart = this.chart; - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: chart.chartArea.left, - y: chart.chartArea.top - }; - - var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'xy', intersect: false}).map(item => item.element); - expect(elements).toEqual([meta0.data[0], meta1.data[0]]); - }); - }); - }); - - describe('dataset mode', function() { - describe('intersect: true', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - it ('should return all items in the dataset of the first item found', function() { - var chart = this.chart; - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: point.x, - y: point.y - }; - - var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual(meta.data); - }); - - it ('should return an empty array if nothing found', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: 0, - y: 0 - }; - - var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true}); - expect(elements).toEqual([]); - }); - }); - - describe('intersect: false', function() { - var data = { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }; - - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: data - }); - }); - - it ('axis: x gets correct items', function() { - var chart = window.acquireChart({ - type: 'bar', - data: data, - options: { - indexAxis: 'y', - } - }); - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: chart.chartArea.left, - y: chart.chartArea.top - }; - - var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'x', intersect: false}).map(item => item.element); - expect(elements).toEqual(chart.getDatasetMeta(0).data); - }); - - it ('axis: y gets correct items', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: chart.chartArea.left, - y: chart.chartArea.top - }; - - var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'y', intersect: false}).map(item => item.element); - expect(elements).toEqual(chart.getDatasetMeta(1).data); - }); - - it ('axis: xy gets correct items', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: chart.chartArea.left, - y: chart.chartArea.top - }; - - var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual(chart.getDatasetMeta(1).data); - }); - }); - }); - - describe('nearest mode', function() { - describe('intersect: false', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 40, 30], - pointRadius: [5, 5, 5], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointRadius: [10, 10, 10], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - 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: chart.chartArea.left, - y: chart.chartArea.top - }; - - // Nearest to 0,0 (top left) will be first point of dataset 2 - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}).map(item => item.element); - 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].x, - y: (meta0.data[1].y + meta1.data[1].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}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[1]]); - }); - }); - - 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].x, - y: meta0.data[0].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}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.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].x + meta0.data[1].x) / 2, - y: meta0.data[0].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}).map(item => item.element); - expect(elements).toEqual([meta0.data[0], meta0.data[1], meta1.data[0], meta1.data[1]]); - }); - }); - - 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].x, - y: meta0.data[2].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}).map(item => item.element); - expect(elements).toEqual([meta0.data[2]]); - }); - - 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].x, - y: meta0.data[1].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}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); - }); - }); - }); - - describe('intersect: true', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - 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.x + 15, - y: point.y - }; - - // Nothing intersects so find nothing - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([]); - - evt = { - type: 'click', - chart: chart, - native: true, - x: point.x, - y: point.y - }; - elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); - 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; - - chart.data.datasets[1].pointRadius = [10, 10, 10]; - - chart.update(); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - - // Halfway between 2 mid points - var pt = { - x: meta0.data[1].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; - - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([meta0.data[1]]); - }); - - 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; - - chart.data.datasets[1].pointRadius = [10, 10, 10]; - - chart.update(); - - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - - // Halfway between 2 mid points - var pt = { - x: meta0.data[1].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; - - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[1]]); - }); - }); - }); - }); - - describe('x mode', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 40, 30], - pointRadius: [5, 10, 5], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointRadius: [10, 10, 10], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - it('should return items at the same x value when intersect is false', 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].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: 0 - }; - - var elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[1]]); - - evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x + 20, - y: 0 - }; - - elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual([]); - }); - - it('should return items at the same x value when intersect is true', 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].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: 0 - }; - - var elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([]); // we don't intersect anything - - evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; - - elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[1]]); - }); - }); - - describe('y mode', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 40, 30], - pointRadius: [5, 10, 5], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointRadius: [10, 10, 10], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - } - }); - }); - - it('should return items at the same y value when intersect is false', 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].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, - x: 0, - y: pt.y, - }; - - var elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); - - evt = { - type: 'click', - chart: chart, - native: true, - x: pt.x, - y: pt.y + 20, // out of range - }; - - elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element); - expect(elements).toEqual([]); - }); - - it('should return items at the same y value when intersect is true', 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].x, - y: meta0.data[1].y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, - x: 0, - y: pt.y - }; - - var elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([]); // we don't intersect anything - - evt = { - type: 'click', - chart: chart, - native: true, - x: pt.x, - y: pt.y, - }; - - elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element); - expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); - }); - }); + describe('point mode', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 20, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + it ('should return all items under the point', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + var point = meta0.data[1]; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: point.x, + y: point.y, + }; + + var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element); + expect(elements).toEqual([point, meta1.data[1]]); + }); + + it ('should return an empty array when no items are found', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: 0, + y: 0 + }; + + var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element); + expect(elements).toEqual([]); + }); + }); + + describe('index mode', function() { + describe('intersect: true', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + it ('gets correct items', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + var point = meta0.data[1]; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: point.x, + y: point.y, + }; + + var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([point, meta1.data[1]]); + }); + + it ('returns empty array when nothing found', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: 0, + y: 0, + }; + + var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([]); + }); + }); + + describe ('intersect: false', function() { + var data = { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }; + + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: data + }); + }); + + it ('axis: x gets correct items', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: chart.chartArea.left, + y: chart.chartArea.top + }; + + var elements = Chart.Interaction.modes.index(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual([meta0.data[0], meta1.data[0]]); + }); + + it ('axis: y gets correct items', function() { + var chart = window.acquireChart({ + type: 'bar', + data: data, + options: { + indexAxis: 'y', + } + }); + + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + var center = meta0.data[0].getCenterPoint(); + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: center.x, + y: center.y + 30, + }; + + var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'y', intersect: false}).map(item => item.element); + expect(elements).toEqual([meta0.data[0], meta1.data[0]]); + }); + + it ('axis: xy gets correct items', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: chart.chartArea.left, + y: chart.chartArea.top + }; + + var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'xy', intersect: false}).map(item => item.element); + expect(elements).toEqual([meta0.data[0], meta1.data[0]]); + }); + }); + }); + + describe('dataset mode', function() { + describe('intersect: true', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + it ('should return all items in the dataset of the first item found', function() { + var chart = this.chart; + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: point.x, + y: point.y + }; + + var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual(meta.data); + }); + + it ('should return an empty array if nothing found', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: 0, + y: 0 + }; + + var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true}); + expect(elements).toEqual([]); + }); + }); + + describe('intersect: false', function() { + var data = { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }; + + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: data + }); + }); + + it ('axis: x gets correct items', function() { + var chart = window.acquireChart({ + type: 'bar', + data: data, + options: { + indexAxis: 'y', + } + }); + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: chart.chartArea.left, + y: chart.chartArea.top + }; + + var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'x', intersect: false}).map(item => item.element); + expect(elements).toEqual(chart.getDatasetMeta(0).data); + }); + + it ('axis: y gets correct items', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: chart.chartArea.left, + y: chart.chartArea.top + }; + + var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'y', intersect: false}).map(item => item.element); + expect(elements).toEqual(chart.getDatasetMeta(1).data); + }); + + it ('axis: xy gets correct items', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: chart.chartArea.left, + y: chart.chartArea.top + }; + + var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual(chart.getDatasetMeta(1).data); + }); + }); + }); + + describe('nearest mode', function() { + describe('intersect: false', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 40, 30], + pointRadius: [5, 5, 5], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointRadius: [10, 10, 10], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + 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: chart.chartArea.left, + y: chart.chartArea.top + }; + + // Nearest to 0,0 (top left) will be first point of dataset 2 + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}).map(item => item.element); + 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].x, + y: (meta0.data[1].y + meta1.data[1].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}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); + }); + + 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].x, + y: meta0.data[0].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}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.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].x + meta0.data[1].x) / 2, + y: meta0.data[0].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}).map(item => item.element); + expect(elements).toEqual([meta0.data[0], meta0.data[1], meta1.data[0], meta1.data[1]]); + }); + }); + + 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].x, + y: meta0.data[2].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}).map(item => item.element); + expect(elements).toEqual([meta0.data[2]]); + }); + + 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].x, + y: meta0.data[1].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}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); + }); + }); + }); + + describe('intersect: true', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + 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.x + 15, + y: point.y + }; + + // Nothing intersects so find nothing + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([]); + + evt = { + type: 'click', + chart: chart, + native: true, + x: point.x, + y: point.y + }; + elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); + 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; + + chart.data.datasets[1].pointRadius = [10, 10, 10]; + + chart.update(); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + + // Halfway between 2 mid points + var pt = { + x: meta0.data[1].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([meta0.data[1]]); + }); + + 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; + + chart.data.datasets[1].pointRadius = [10, 10, 10]; + + chart.update(); + + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + // Halfway between 2 mid points + var pt = { + x: meta0.data[1].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); + }); + }); + }); + + describe('x mode', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 40, 30], + pointRadius: [5, 10, 5], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointRadius: [10, 10, 10], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + it('should return items at the same x value when intersect is false', 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].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: 0 + }; + + var elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + + evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x + 20, + y: 0 + }; + + elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual([]); + }); + + it('should return items at the same x value when intersect is true', 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].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: 0 + }; + + var elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([]); // we don't intersect anything + + evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); + }); + + describe('y mode', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 40, 30], + pointRadius: [5, 10, 5], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointRadius: [10, 10, 10], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + } + }); + }); + + it('should return items at the same y value when intersect is false', 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].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, + x: 0, + y: pt.y, + }; + + var elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); + + evt = { + type: 'click', + chart: chart, + native: true, + x: pt.x, + y: pt.y + 20, // out of range + }; + + elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element); + expect(elements).toEqual([]); + }); + + it('should return items at the same y value when intersect is true', 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].x, + y: meta0.data[1].y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, + x: 0, + y: pt.y + }; + + var elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([]); // we don't intersect anything + + evt = { + type: 'click', + chart: chart, + native: true, + x: pt.x, + y: pt.y, + }; + + elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element); + expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); + }); + }); }); diff --git a/test/specs/core.layouts.tests.js b/test/specs/core.layouts.tests.js index cf70ddda63e..ee70b2ea79b 100644 --- a/test/specs/core.layouts.tests.js +++ b/test/specs/core.layouts.tests.js @@ -1,572 +1,572 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Chart.layouts', function() { - describe('auto', jasmine.fixture.specs('core.layouts')); - - it('should be exposed through Chart.layouts', function() { - expect(Chart.layouts).toBeDefined(); - expect(typeof Chart.layouts).toBe('object'); - expect(Chart.layouts.addBox).toBeDefined(); - expect(Chart.layouts.removeBox).toBeDefined(); - expect(Chart.layouts.configure).toBeDefined(); - expect(Chart.layouts.update).toBeDefined(); - }); - - it('should fit a simple chart with 2 scales', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(120); - expect(chart.chartArea.left).toBeCloseToPixel(34); - expect(chart.chartArea.right).toBeCloseToPixel(247); - expect(chart.chartArea.top).toBeCloseToPixel(32); - - // Is xScale at the right spot - expect(chart.scales.x.bottom).toBeCloseToPixel(150); - expect(chart.scales.x.left).toBeCloseToPixel(34); - expect(chart.scales.x.right).toBeCloseToPixel(247); - expect(chart.scales.x.top).toBeCloseToPixel(120); - expect(chart.scales.x.labelRotation).toBeCloseTo(0); - - // Is yScale at the right spot - expect(chart.scales.y.bottom).toBeCloseToPixel(120); - expect(chart.scales.y.left).toBeCloseToPixel(0); - expect(chart.scales.y.right).toBeCloseToPixel(34); - expect(chart.scales.y.top).toBeCloseToPixel(32); - expect(chart.scales.y.labelRotation).toBeCloseTo(0); - }); - - it('should fit scales that are in the top and right positions', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'top' - }, - y: { - type: 'linear', - position: 'right' - } - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(142); - expect(chart.chartArea.left).toBeCloseToPixel(3); - expect(chart.chartArea.right).toBeCloseToPixel(216); - expect(chart.chartArea.top).toBeCloseToPixel(62); - - // Is xScale at the right spot - expect(chart.scales.x.bottom).toBeCloseToPixel(62); - expect(chart.scales.x.left).toBeCloseToPixel(3); - expect(chart.scales.x.right).toBeCloseToPixel(216); - expect(chart.scales.x.top).toBeCloseToPixel(32); - expect(chart.scales.x.labelRotation).toBeCloseTo(0); - - // Is yScale at the right spot - expect(chart.scales.y.bottom).toBeCloseToPixel(142); - expect(chart.scales.y.left).toBeCloseToPixel(216); - expect(chart.scales.y.right).toBeCloseToPixel(250); - expect(chart.scales.y.top).toBeCloseToPixel(62); - expect(chart.scales.y.labelRotation).toBeCloseTo(0); - }); - - it('should fit scales that overlap the chart area', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78, -10] - }, { - data: [-19, -20, 0, -99, -50, 0] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(512); - expect(chart.chartArea.left).toBeCloseToPixel(0); - expect(chart.chartArea.right).toBeCloseToPixel(512); - expect(chart.chartArea.top).toBeCloseToPixel(32); - - var scale = chart.scales.r; - expect(scale.bottom).toBeCloseToPixel(512); - expect(scale.left).toBeCloseToPixel(0); - expect(scale.right).toBeCloseToPixel(512); - expect(scale.top).toBeCloseToPixel(32); - expect(scale.width).toBeCloseToPixel(512); - expect(scale.height).toBeCloseToPixel(480); - }); - - it('should fit multiple axes in the same position', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, 25, 78, -10] - }, { - yAxisID: 'y2', - data: [-19, -20, 0, -99, -50, 0] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category' - }, - y: { - type: 'linear' - }, - y2: { - type: 'linear' - } - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(118); - expect(chart.chartArea.left).toBeCloseToPixel(73); - expect(chart.chartArea.right).toBeCloseToPixel(247); - expect(chart.chartArea.top).toBeCloseToPixel(32); - - // Is xScale at the right spot - expect(chart.scales.x.bottom).toBeCloseToPixel(150); - expect(chart.scales.x.left).toBeCloseToPixel(73); - expect(chart.scales.x.right).toBeCloseToPixel(247); - expect(chart.scales.x.top).toBeCloseToPixel(118); - expect(chart.scales.x.labelRotation).toBeCloseTo(40, -1); - - // Are yScales at the right spot - expect(chart.scales.y.bottom).toBeCloseToPixel(118); - expect(chart.scales.y.left).toBeCloseToPixel(41); - expect(chart.scales.y.right).toBeCloseToPixel(73); - expect(chart.scales.y.top).toBeCloseToPixel(32); - expect(chart.scales.y.labelRotation).toBeCloseTo(0); - - expect(chart.scales.y2.bottom).toBeCloseToPixel(118); - expect(chart.scales.y2.left).toBeCloseToPixel(0); - expect(chart.scales.y2.right).toBeCloseToPixel(41); - expect(chart.scales.y2.top).toBeCloseToPixel(32); - expect(chart.scales.y2.labelRotation).toBeCloseTo(0); - }); - - it ('should fit a full width box correctly', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - xAxisID: 'x', - data: [10, 5, 0, 25, 78, -10] - }, { - xAxisID: 'x2', - data: [-19, -20, 0, -99, -50, 0] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - offset: false - }, - x2: { - type: 'category', - position: 'top', - fullSize: true, - offset: false - }, - y: { - type: 'linear' - } - } - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(484); - expect(chart.chartArea.left).toBeCloseToPixel(40); - expect(chart.chartArea.right).toBeCloseToPixel(496); - expect(chart.chartArea.top).toBeCloseToPixel(62); - - // Are xScales at the right spot - expect(chart.scales.x.bottom).toBeCloseToPixel(512); - expect(chart.scales.x.left).toBeCloseToPixel(40); - expect(chart.scales.x.right).toBeCloseToPixel(496); - expect(chart.scales.x.top).toBeCloseToPixel(484); - - expect(chart.scales.x2.bottom).toBeCloseToPixel(62); - expect(chart.scales.x2.left).toBeCloseToPixel(0); - expect(chart.scales.x2.right).toBeCloseToPixel(512); - expect(chart.scales.x2.top).toBeCloseToPixel(32); - - // Is yScale at the right spot - expect(chart.scales.y.bottom).toBeCloseToPixel(484); - expect(chart.scales.y.left).toBeCloseToPixel(0); - expect(chart.scales.y.right).toBeCloseToPixel(40); - expect(chart.scales.y.top).toBeCloseToPixel(62); - }); - - describe('padding settings', function() { - it('should apply a single padding to all dimensions', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - { - data: [10, 5, 0, 25, 78, -10] - } - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false - } - }, - plugins: { - legend: false, - title: false - }, - layout: { - padding: 10 - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(140); - expect(chart.chartArea.left).toBeCloseToPixel(10); - expect(chart.chartArea.right).toBeCloseToPixel(240); - expect(chart.chartArea.top).toBeCloseToPixel(10); - }); - - it('should apply padding in all positions', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - { - data: [10, 5, 0, 25, 78, -10] - } - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false - } - }, - plugins: { - legend: false, - title: false - }, - layout: { - padding: { - left: 5, - right: 15, - top: 8, - bottom: 12 - } - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(138); - expect(chart.chartArea.left).toBeCloseToPixel(5); - expect(chart.chartArea.right).toBeCloseToPixel(235); - expect(chart.chartArea.top).toBeCloseToPixel(8); - }); - - it('should default to 0 padding if no dimensions specified', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - { - data: [10, 5, 0, 25, 78, -10] - } - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - display: false - }, - y: { - type: 'linear', - display: false - } - }, - plugins: { - legend: false, - title: false - }, - layout: { - padding: {} - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - expect(chart.chartArea.bottom).toBeCloseToPixel(150); - expect(chart.chartArea.left).toBeCloseToPixel(0); - expect(chart.chartArea.right).toBeCloseToPixel(250); - expect(chart.chartArea.top).toBeCloseToPixel(0); - }); - }); - - describe('ordering by weight', function() { - it('should keep higher weights outside', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - { - data: [10, 5, 0, 25, 78, -10] - } - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - plugins: { - legend: { - display: true, - position: 'left', - }, - title: { - display: true, - position: 'bottom', - }, - } - }, - }, { - canvas: { - height: 150, - width: 250 - } - }); - - var xAxis = chart.scales.x; - var yAxis = chart.scales.y; - var legend = chart.legend; - var title = chart.titleBlock; - - expect(yAxis.left).toBe(legend.right); - expect(xAxis.bottom).toBe(title.top); - }); - - it('should correctly set weights of scales and order them', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - { - data: [10, 5, 0, 25, 78, -10] - } - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom', - display: true, - weight: 1 - }, - x1: { - type: 'category', - position: 'bottom', - display: true, - weight: 2 - }, - x2: { - type: 'category', - position: 'bottom', - display: true - }, - x3: { - type: 'category', - display: true, - position: 'top', - weight: 1 - }, - x4: { - type: 'category', - display: true, - position: 'top', - weight: 2 - }, - y: { - type: 'linear', - display: true, - weight: 1 - }, - y1: { - type: 'linear', - position: 'left', - display: true, - weight: 2 - }, - y2: { - type: 'linear', - position: 'left', - display: true - }, - y3: { - type: 'linear', - display: true, - position: 'right', - weight: 1 - }, - y4: { - type: 'linear', - display: true, - position: 'right', - weight: 2 - } - } - } - }, { - canvas: { - height: 150, - width: 250 - } - }); - - var xScale0 = chart.scales.x; - var xScale1 = chart.scales.x1; - var xScale2 = chart.scales.x2; - var xScale3 = chart.scales.x3; - var xScale4 = chart.scales.x4; - - var yScale0 = chart.scales.y; - var yScale1 = chart.scales.y1; - var yScale2 = chart.scales.y2; - var yScale3 = chart.scales.y3; - var yScale4 = chart.scales.y4; - - expect(xScale0.weight).toBe(1); - expect(xScale1.weight).toBe(2); - expect(xScale2.weight).toBe(0); - - expect(xScale3.weight).toBe(1); - expect(xScale4.weight).toBe(2); - - expect(yScale0.weight).toBe(1); - expect(yScale1.weight).toBe(2); - expect(yScale2.weight).toBe(0); - - expect(yScale3.weight).toBe(1); - expect(yScale4.weight).toBe(2); - - var isOrderCorrect = false; - - // bottom axes - isOrderCorrect = xScale2.top < xScale0.top && xScale0.top < xScale1.top; - expect(isOrderCorrect).toBe(true); - - // top axes - isOrderCorrect = xScale4.top < xScale3.top; - expect(isOrderCorrect).toBe(true); - - // left axes - isOrderCorrect = yScale1.left < yScale0.left && yScale0.left < yScale2.left; - expect(isOrderCorrect).toBe(true); - - // right axes - isOrderCorrect = yScale3.left < yScale4.left; - expect(isOrderCorrect).toBe(true); - }); - }); - - describe('box sizing', function() { - it('should correctly compute y-axis width to fit labels', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - labels: ['tick 1', 'tick 2', 'tick 3', 'tick 4', 'tick 5'], - datasets: [{ - data: [0, 2.25, 1.5, 1.25, 2.5] - }], - }, - options: { - plugins: { - legend: false - }, - }, - }, { - canvas: { - height: 256, - width: 256 - } - }); - var yAxis = chart.scales.y; - - // issue #4441: y-axis labels partially hidden. - // minimum horizontal space required to fit labels - expect(yAxis.width).toBeCloseToPixel(33); - expect(getLabels(yAxis)).toEqual(['0', '0.5', '1.0', '1.5', '2.0', '2.5']); - }); - }); + describe('auto', jasmine.fixture.specs('core.layouts')); + + it('should be exposed through Chart.layouts', function() { + expect(Chart.layouts).toBeDefined(); + expect(typeof Chart.layouts).toBe('object'); + expect(Chart.layouts.addBox).toBeDefined(); + expect(Chart.layouts.removeBox).toBeDefined(); + expect(Chart.layouts.configure).toBeDefined(); + expect(Chart.layouts.update).toBeDefined(); + }); + + it('should fit a simple chart with 2 scales', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(120); + expect(chart.chartArea.left).toBeCloseToPixel(34); + expect(chart.chartArea.right).toBeCloseToPixel(247); + expect(chart.chartArea.top).toBeCloseToPixel(32); + + // Is xScale at the right spot + expect(chart.scales.x.bottom).toBeCloseToPixel(150); + expect(chart.scales.x.left).toBeCloseToPixel(34); + expect(chart.scales.x.right).toBeCloseToPixel(247); + expect(chart.scales.x.top).toBeCloseToPixel(120); + expect(chart.scales.x.labelRotation).toBeCloseTo(0); + + // Is yScale at the right spot + expect(chart.scales.y.bottom).toBeCloseToPixel(120); + expect(chart.scales.y.left).toBeCloseToPixel(0); + expect(chart.scales.y.right).toBeCloseToPixel(34); + expect(chart.scales.y.top).toBeCloseToPixel(32); + expect(chart.scales.y.labelRotation).toBeCloseTo(0); + }); + + it('should fit scales that are in the top and right positions', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'top' + }, + y: { + type: 'linear', + position: 'right' + } + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(142); + expect(chart.chartArea.left).toBeCloseToPixel(3); + expect(chart.chartArea.right).toBeCloseToPixel(216); + expect(chart.chartArea.top).toBeCloseToPixel(62); + + // Is xScale at the right spot + expect(chart.scales.x.bottom).toBeCloseToPixel(62); + expect(chart.scales.x.left).toBeCloseToPixel(3); + expect(chart.scales.x.right).toBeCloseToPixel(216); + expect(chart.scales.x.top).toBeCloseToPixel(32); + expect(chart.scales.x.labelRotation).toBeCloseTo(0); + + // Is yScale at the right spot + expect(chart.scales.y.bottom).toBeCloseToPixel(142); + expect(chart.scales.y.left).toBeCloseToPixel(216); + expect(chart.scales.y.right).toBeCloseToPixel(250); + expect(chart.scales.y.top).toBeCloseToPixel(62); + expect(chart.scales.y.labelRotation).toBeCloseTo(0); + }); + + it('should fit scales that overlap the chart area', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78, -10] + }, { + data: [-19, -20, 0, -99, -50, 0] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(512); + expect(chart.chartArea.left).toBeCloseToPixel(0); + expect(chart.chartArea.right).toBeCloseToPixel(512); + expect(chart.chartArea.top).toBeCloseToPixel(32); + + var scale = chart.scales.r; + expect(scale.bottom).toBeCloseToPixel(512); + expect(scale.left).toBeCloseToPixel(0); + expect(scale.right).toBeCloseToPixel(512); + expect(scale.top).toBeCloseToPixel(32); + expect(scale.width).toBeCloseToPixel(512); + expect(scale.height).toBeCloseToPixel(480); + }); + + it('should fit multiple axes in the same position', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, 25, 78, -10] + }, { + yAxisID: 'y2', + data: [-19, -20, 0, -99, -50, 0] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category' + }, + y: { + type: 'linear' + }, + y2: { + type: 'linear' + } + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(118); + expect(chart.chartArea.left).toBeCloseToPixel(73); + expect(chart.chartArea.right).toBeCloseToPixel(247); + expect(chart.chartArea.top).toBeCloseToPixel(32); + + // Is xScale at the right spot + expect(chart.scales.x.bottom).toBeCloseToPixel(150); + expect(chart.scales.x.left).toBeCloseToPixel(73); + expect(chart.scales.x.right).toBeCloseToPixel(247); + expect(chart.scales.x.top).toBeCloseToPixel(118); + expect(chart.scales.x.labelRotation).toBeCloseTo(40, -1); + + // Are yScales at the right spot + expect(chart.scales.y.bottom).toBeCloseToPixel(118); + expect(chart.scales.y.left).toBeCloseToPixel(41); + expect(chart.scales.y.right).toBeCloseToPixel(73); + expect(chart.scales.y.top).toBeCloseToPixel(32); + expect(chart.scales.y.labelRotation).toBeCloseTo(0); + + expect(chart.scales.y2.bottom).toBeCloseToPixel(118); + expect(chart.scales.y2.left).toBeCloseToPixel(0); + expect(chart.scales.y2.right).toBeCloseToPixel(41); + expect(chart.scales.y2.top).toBeCloseToPixel(32); + expect(chart.scales.y2.labelRotation).toBeCloseTo(0); + }); + + it ('should fit a full width box correctly', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + xAxisID: 'x', + data: [10, 5, 0, 25, 78, -10] + }, { + xAxisID: 'x2', + data: [-19, -20, 0, -99, -50, 0] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + offset: false + }, + x2: { + type: 'category', + position: 'top', + fullSize: true, + offset: false + }, + y: { + type: 'linear' + } + } + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(484); + expect(chart.chartArea.left).toBeCloseToPixel(40); + expect(chart.chartArea.right).toBeCloseToPixel(496); + expect(chart.chartArea.top).toBeCloseToPixel(62); + + // Are xScales at the right spot + expect(chart.scales.x.bottom).toBeCloseToPixel(512); + expect(chart.scales.x.left).toBeCloseToPixel(40); + expect(chart.scales.x.right).toBeCloseToPixel(496); + expect(chart.scales.x.top).toBeCloseToPixel(484); + + expect(chart.scales.x2.bottom).toBeCloseToPixel(62); + expect(chart.scales.x2.left).toBeCloseToPixel(0); + expect(chart.scales.x2.right).toBeCloseToPixel(512); + expect(chart.scales.x2.top).toBeCloseToPixel(32); + + // Is yScale at the right spot + expect(chart.scales.y.bottom).toBeCloseToPixel(484); + expect(chart.scales.y.left).toBeCloseToPixel(0); + expect(chart.scales.y.right).toBeCloseToPixel(40); + expect(chart.scales.y.top).toBeCloseToPixel(62); + }); + + describe('padding settings', function() { + it('should apply a single padding to all dimensions', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + { + data: [10, 5, 0, 25, 78, -10] + } + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false + } + }, + plugins: { + legend: false, + title: false + }, + layout: { + padding: 10 + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(140); + expect(chart.chartArea.left).toBeCloseToPixel(10); + expect(chart.chartArea.right).toBeCloseToPixel(240); + expect(chart.chartArea.top).toBeCloseToPixel(10); + }); + + it('should apply padding in all positions', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + { + data: [10, 5, 0, 25, 78, -10] + } + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false + } + }, + plugins: { + legend: false, + title: false + }, + layout: { + padding: { + left: 5, + right: 15, + top: 8, + bottom: 12 + } + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(138); + expect(chart.chartArea.left).toBeCloseToPixel(5); + expect(chart.chartArea.right).toBeCloseToPixel(235); + expect(chart.chartArea.top).toBeCloseToPixel(8); + }); + + it('should default to 0 padding if no dimensions specified', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + { + data: [10, 5, 0, 25, 78, -10] + } + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + display: false + }, + y: { + type: 'linear', + display: false + } + }, + plugins: { + legend: false, + title: false + }, + layout: { + padding: {} + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(150); + expect(chart.chartArea.left).toBeCloseToPixel(0); + expect(chart.chartArea.right).toBeCloseToPixel(250); + expect(chart.chartArea.top).toBeCloseToPixel(0); + }); + }); + + describe('ordering by weight', function() { + it('should keep higher weights outside', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + { + data: [10, 5, 0, 25, 78, -10] + } + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + plugins: { + legend: { + display: true, + position: 'left', + }, + title: { + display: true, + position: 'bottom', + }, + } + }, + }, { + canvas: { + height: 150, + width: 250 + } + }); + + var xAxis = chart.scales.x; + var yAxis = chart.scales.y; + var legend = chart.legend; + var title = chart.titleBlock; + + expect(yAxis.left).toBe(legend.right); + expect(xAxis.bottom).toBe(title.top); + }); + + it('should correctly set weights of scales and order them', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [ + { + data: [10, 5, 0, 25, 78, -10] + } + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom', + display: true, + weight: 1 + }, + x1: { + type: 'category', + position: 'bottom', + display: true, + weight: 2 + }, + x2: { + type: 'category', + position: 'bottom', + display: true + }, + x3: { + type: 'category', + display: true, + position: 'top', + weight: 1 + }, + x4: { + type: 'category', + display: true, + position: 'top', + weight: 2 + }, + y: { + type: 'linear', + display: true, + weight: 1 + }, + y1: { + type: 'linear', + position: 'left', + display: true, + weight: 2 + }, + y2: { + type: 'linear', + position: 'left', + display: true + }, + y3: { + type: 'linear', + display: true, + position: 'right', + weight: 1 + }, + y4: { + type: 'linear', + display: true, + position: 'right', + weight: 2 + } + } + } + }, { + canvas: { + height: 150, + width: 250 + } + }); + + var xScale0 = chart.scales.x; + var xScale1 = chart.scales.x1; + var xScale2 = chart.scales.x2; + var xScale3 = chart.scales.x3; + var xScale4 = chart.scales.x4; + + var yScale0 = chart.scales.y; + var yScale1 = chart.scales.y1; + var yScale2 = chart.scales.y2; + var yScale3 = chart.scales.y3; + var yScale4 = chart.scales.y4; + + expect(xScale0.weight).toBe(1); + expect(xScale1.weight).toBe(2); + expect(xScale2.weight).toBe(0); + + expect(xScale3.weight).toBe(1); + expect(xScale4.weight).toBe(2); + + expect(yScale0.weight).toBe(1); + expect(yScale1.weight).toBe(2); + expect(yScale2.weight).toBe(0); + + expect(yScale3.weight).toBe(1); + expect(yScale4.weight).toBe(2); + + var isOrderCorrect = false; + + // bottom axes + isOrderCorrect = xScale2.top < xScale0.top && xScale0.top < xScale1.top; + expect(isOrderCorrect).toBe(true); + + // top axes + isOrderCorrect = xScale4.top < xScale3.top; + expect(isOrderCorrect).toBe(true); + + // left axes + isOrderCorrect = yScale1.left < yScale0.left && yScale0.left < yScale2.left; + expect(isOrderCorrect).toBe(true); + + // right axes + isOrderCorrect = yScale3.left < yScale4.left; + expect(isOrderCorrect).toBe(true); + }); + }); + + describe('box sizing', function() { + it('should correctly compute y-axis width to fit labels', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + labels: ['tick 1', 'tick 2', 'tick 3', 'tick 4', 'tick 5'], + datasets: [{ + data: [0, 2.25, 1.5, 1.25, 2.5] + }], + }, + options: { + plugins: { + legend: false + }, + }, + }, { + canvas: { + height: 256, + width: 256 + } + }); + var yAxis = chart.scales.y; + + // issue #4441: y-axis labels partially hidden. + // minimum horizontal space required to fit labels + expect(yAxis.width).toBeCloseToPixel(33); + expect(getLabels(yAxis)).toEqual(['0', '0.5', '1.0', '1.5', '2.0', '2.5']); + }); + }); }); diff --git a/test/specs/core.plugin.tests.js b/test/specs/core.plugin.tests.js index af93dc53af3..d38e55619d8 100644 --- a/test/specs/core.plugin.tests.js +++ b/test/specs/core.plugin.tests.js @@ -1,365 +1,365 @@ describe('Chart.plugins', function() { - describe('Chart.notifyPlugins', function() { - it('should call inline plugins with arguments', function() { - var plugin = {hook: function() {}}; - var chart = window.acquireChart({ - plugins: [plugin] - }); - var args = {value: 42}; - - spyOn(plugin, 'hook'); - - chart.notifyPlugins('hook', args); - expect(plugin.hook.calls.count()).toBe(1); - expect(plugin.hook.calls.first().args[0]).toBe(chart); - expect(plugin.hook.calls.first().args[1]).toBe(args); - expect(plugin.hook.calls.first().args[2]).toEqual({}); - }); - - it('should call global plugins with arguments', function() { - var plugin = {id: 'a', hook: function() {}}; - var chart = window.acquireChart({}); - var args = {value: 42}; - - spyOn(plugin, 'hook'); - - Chart.register(plugin); - chart.notifyPlugins('hook', args); - expect(plugin.hook.calls.count()).toBe(1); - expect(plugin.hook.calls.first().args[0]).toBe(chart); - expect(plugin.hook.calls.first().args[1]).toBe(args); - expect(plugin.hook.calls.first().args[2]).toEqual({}); - Chart.unregister(plugin); - }); - - it('should call plugin only once even if registered multiple times', function() { - var plugin = {id: 'test', hook: function() {}}; - var chart = window.acquireChart({ - plugins: [plugin, plugin] - }); - - spyOn(plugin, 'hook'); - - Chart.register([plugin, plugin]); - chart.notifyPlugins('hook'); - expect(plugin.hook.calls.count()).toBe(1); - Chart.unregister(plugin); - }); - - it('should call plugins in the correct order (global first)', function() { - var results = []; - var chart = window.acquireChart({ - plugins: [{ - hook: function() { - results.push(1); - } - }, { - hook: function() { - results.push(2); - } - }, { - hook: function() { - results.push(3); - } - }] - }); - - var plugins = [{ - id: 'a', - hook: function() { - results.push(4); - } - }, { - id: 'b', - hook: function() { - results.push(5); - } - }, { - id: 'c', - hook: function() { - results.push(6); - } - }]; - Chart.register(plugins); - - var ret = chart.notifyPlugins('hook'); - expect(ret).toBeTruthy(); - expect(results).toEqual([4, 5, 6, 1, 2, 3]); - Chart.unregister(plugins); - }); - - it('should return TRUE if no plugin explicitly returns FALSE', function() { - var chart = window.acquireChart({ - plugins: [{ - hook: function() {} - }, { - hook: function() { - return null; - } - }, { - hook: function() { - return 0; - } - }, { - hook: function() { - return true; - } - }, { - hook: function() { - return 1; - } - }] - }); - - var plugins = chart.config.plugins; - plugins.forEach(function(plugin) { - spyOn(plugin, 'hook').and.callThrough(); - }); - - var ret = chart.notifyPlugins('hook'); - expect(ret).toBeTruthy(); - plugins.forEach(function(plugin) { - expect(plugin.hook).toHaveBeenCalled(); - }); - }); - - it('should return FALSE if any plugin explicitly returns FALSE', function() { - var chart = window.acquireChart({ - plugins: [{ - hook: function() {} - }, { - hook: function() { - return null; - } - }, { - hook: function() { - return false; - } - }, { - hook: function() { - return 42; - } - }, { - hook: function() { - return 'bar'; - } - }] - }); - - var plugins = chart.config.plugins; - plugins.forEach(function(plugin) { - spyOn(plugin, 'hook').and.callThrough(); - }); - - var ret = chart.notifyPlugins('hook', {cancelable: true}); - expect(ret).toBeFalsy(); - expect(plugins[0].hook).toHaveBeenCalled(); - expect(plugins[1].hook).toHaveBeenCalled(); - expect(plugins[2].hook).toHaveBeenCalled(); - expect(plugins[3].hook).not.toHaveBeenCalled(); - expect(plugins[4].hook).not.toHaveBeenCalled(); - }); - }); - - describe('config.options.plugins', function() { - it('should call plugins with options at last argument', function() { - var plugin = {id: 'foo', hook: function() {}}; - - var chart = window.acquireChart({ - options: { - plugins: { - foo: {a: '123'}, - } - } - }); - - spyOn(plugin, 'hook'); - - Chart.register(plugin); - chart.notifyPlugins('hook'); - chart.notifyPlugins('hook', {arg1: 'bla'}); - chart.notifyPlugins('hook', {arg1: 'bla', arg2: 42}); - - expect(plugin.hook.calls.count()).toBe(3); - expect(plugin.hook.calls.argsFor(0)[2]).toEqual({a: '123'}); - expect(plugin.hook.calls.argsFor(1)[2]).toEqual({a: '123'}); - expect(plugin.hook.calls.argsFor(2)[2]).toEqual({a: '123'}); - - Chart.unregister(plugin); - }); - - it('should call plugins with options associated to their identifier', function() { - var plugins = { - a: {id: 'a', hook: function() {}}, - b: {id: 'b', hook: function() {}}, - c: {id: 'c', hook: function() {}} - }; - - Chart.register(plugins.a); - - var chart = window.acquireChart({ - plugins: [plugins.b, plugins.c], - options: { - plugins: { - a: {a: '123'}, - b: {b: '456'}, - c: {c: '789'} - } - } - }); - - spyOn(plugins.a, 'hook'); - spyOn(plugins.b, 'hook'); - spyOn(plugins.c, 'hook'); - - chart.notifyPlugins('hook'); - - expect(plugins.a.hook).toHaveBeenCalled(); - expect(plugins.b.hook).toHaveBeenCalled(); - expect(plugins.c.hook).toHaveBeenCalled(); - expect(plugins.a.hook.calls.first().args[2]).toEqual({a: '123'}); - expect(plugins.b.hook.calls.first().args[2]).toEqual({b: '456'}); - expect(plugins.c.hook.calls.first().args[2]).toEqual({c: '789'}); - - Chart.unregister(plugins.a); - }); - - it('should not called plugins when config.options.plugins.{id} is FALSE', function() { - var plugins = { - a: {id: 'a', hook: function() {}}, - b: {id: 'b', hook: function() {}}, - c: {id: 'c', hook: function() {}} - }; - - Chart.register(plugins.a); - - var chart = window.acquireChart({ - plugins: [plugins.b, plugins.c], - options: { - plugins: { - a: false, - b: false - } - } - }); - - spyOn(plugins.a, 'hook'); - spyOn(plugins.b, 'hook'); - spyOn(plugins.c, 'hook'); - - chart.notifyPlugins('hook'); - - expect(plugins.a.hook).not.toHaveBeenCalled(); - expect(plugins.b.hook).not.toHaveBeenCalled(); - expect(plugins.c.hook).toHaveBeenCalled(); - - Chart.unregister(plugins.a); - }); - - it('should call plugins with default options when plugin options is TRUE', function() { - var plugin = {id: 'a', hook: function() {}, defaults: {a: 42}}; - - Chart.register(plugin); - - var chart = window.acquireChart({ - options: { - plugins: { - a: true - } - } - }); - - spyOn(plugin, 'hook'); - - chart.notifyPlugins('hook'); - - expect(plugin.hook).toHaveBeenCalled(); - expect(plugin.hook.calls.first().args[2]).toEqual({a: 42}); - - Chart.unregister(plugin); - }); + describe('Chart.notifyPlugins', function() { + it('should call inline plugins with arguments', function() { + var plugin = {hook: function() {}}; + var chart = window.acquireChart({ + plugins: [plugin] + }); + var args = {value: 42}; + + spyOn(plugin, 'hook'); + + chart.notifyPlugins('hook', args); + expect(plugin.hook.calls.count()).toBe(1); + expect(plugin.hook.calls.first().args[0]).toBe(chart); + expect(plugin.hook.calls.first().args[1]).toBe(args); + expect(plugin.hook.calls.first().args[2]).toEqual({}); + }); + + it('should call global plugins with arguments', function() { + var plugin = {id: 'a', hook: function() {}}; + var chart = window.acquireChart({}); + var args = {value: 42}; + + spyOn(plugin, 'hook'); + + Chart.register(plugin); + chart.notifyPlugins('hook', args); + expect(plugin.hook.calls.count()).toBe(1); + expect(plugin.hook.calls.first().args[0]).toBe(chart); + expect(plugin.hook.calls.first().args[1]).toBe(args); + expect(plugin.hook.calls.first().args[2]).toEqual({}); + Chart.unregister(plugin); + }); + + it('should call plugin only once even if registered multiple times', function() { + var plugin = {id: 'test', hook: function() {}}; + var chart = window.acquireChart({ + plugins: [plugin, plugin] + }); + + spyOn(plugin, 'hook'); + + Chart.register([plugin, plugin]); + chart.notifyPlugins('hook'); + expect(plugin.hook.calls.count()).toBe(1); + Chart.unregister(plugin); + }); + + it('should call plugins in the correct order (global first)', function() { + var results = []; + var chart = window.acquireChart({ + plugins: [{ + hook: function() { + results.push(1); + } + }, { + hook: function() { + results.push(2); + } + }, { + hook: function() { + results.push(3); + } + }] + }); + + var plugins = [{ + id: 'a', + hook: function() { + results.push(4); + } + }, { + id: 'b', + hook: function() { + results.push(5); + } + }, { + id: 'c', + hook: function() { + results.push(6); + } + }]; + Chart.register(plugins); + + var ret = chart.notifyPlugins('hook'); + expect(ret).toBeTruthy(); + expect(results).toEqual([4, 5, 6, 1, 2, 3]); + Chart.unregister(plugins); + }); + + it('should return TRUE if no plugin explicitly returns FALSE', function() { + var chart = window.acquireChart({ + plugins: [{ + hook: function() {} + }, { + hook: function() { + return null; + } + }, { + hook: function() { + return 0; + } + }, { + hook: function() { + return true; + } + }, { + hook: function() { + return 1; + } + }] + }); + + var plugins = chart.config.plugins; + plugins.forEach(function(plugin) { + spyOn(plugin, 'hook').and.callThrough(); + }); + + var ret = chart.notifyPlugins('hook'); + expect(ret).toBeTruthy(); + plugins.forEach(function(plugin) { + expect(plugin.hook).toHaveBeenCalled(); + }); + }); + + it('should return FALSE if any plugin explicitly returns FALSE', function() { + var chart = window.acquireChart({ + plugins: [{ + hook: function() {} + }, { + hook: function() { + return null; + } + }, { + hook: function() { + return false; + } + }, { + hook: function() { + return 42; + } + }, { + hook: function() { + return 'bar'; + } + }] + }); + + var plugins = chart.config.plugins; + plugins.forEach(function(plugin) { + spyOn(plugin, 'hook').and.callThrough(); + }); + + var ret = chart.notifyPlugins('hook', {cancelable: true}); + expect(ret).toBeFalsy(); + expect(plugins[0].hook).toHaveBeenCalled(); + expect(plugins[1].hook).toHaveBeenCalled(); + expect(plugins[2].hook).toHaveBeenCalled(); + expect(plugins[3].hook).not.toHaveBeenCalled(); + expect(plugins[4].hook).not.toHaveBeenCalled(); + }); + }); + + describe('config.options.plugins', function() { + it('should call plugins with options at last argument', function() { + var plugin = {id: 'foo', hook: function() {}}; + + var chart = window.acquireChart({ + options: { + plugins: { + foo: {a: '123'}, + } + } + }); + + spyOn(plugin, 'hook'); + + Chart.register(plugin); + chart.notifyPlugins('hook'); + chart.notifyPlugins('hook', {arg1: 'bla'}); + chart.notifyPlugins('hook', {arg1: 'bla', arg2: 42}); + + expect(plugin.hook.calls.count()).toBe(3); + expect(plugin.hook.calls.argsFor(0)[2]).toEqual({a: '123'}); + expect(plugin.hook.calls.argsFor(1)[2]).toEqual({a: '123'}); + expect(plugin.hook.calls.argsFor(2)[2]).toEqual({a: '123'}); + + Chart.unregister(plugin); + }); + + it('should call plugins with options associated to their identifier', function() { + var plugins = { + a: {id: 'a', hook: function() {}}, + b: {id: 'b', hook: function() {}}, + c: {id: 'c', hook: function() {}} + }; + + Chart.register(plugins.a); + + var chart = window.acquireChart({ + plugins: [plugins.b, plugins.c], + options: { + plugins: { + a: {a: '123'}, + b: {b: '456'}, + c: {c: '789'} + } + } + }); + + spyOn(plugins.a, 'hook'); + spyOn(plugins.b, 'hook'); + spyOn(plugins.c, 'hook'); + + chart.notifyPlugins('hook'); + + expect(plugins.a.hook).toHaveBeenCalled(); + expect(plugins.b.hook).toHaveBeenCalled(); + expect(plugins.c.hook).toHaveBeenCalled(); + expect(plugins.a.hook.calls.first().args[2]).toEqual({a: '123'}); + expect(plugins.b.hook.calls.first().args[2]).toEqual({b: '456'}); + expect(plugins.c.hook.calls.first().args[2]).toEqual({c: '789'}); + + Chart.unregister(plugins.a); + }); + + it('should not called plugins when config.options.plugins.{id} is FALSE', function() { + var plugins = { + a: {id: 'a', hook: function() {}}, + b: {id: 'b', hook: function() {}}, + c: {id: 'c', hook: function() {}} + }; + + Chart.register(plugins.a); + + var chart = window.acquireChart({ + plugins: [plugins.b, plugins.c], + options: { + plugins: { + a: false, + b: false + } + } + }); + + spyOn(plugins.a, 'hook'); + spyOn(plugins.b, 'hook'); + spyOn(plugins.c, 'hook'); + + chart.notifyPlugins('hook'); + + expect(plugins.a.hook).not.toHaveBeenCalled(); + expect(plugins.b.hook).not.toHaveBeenCalled(); + expect(plugins.c.hook).toHaveBeenCalled(); + + Chart.unregister(plugins.a); + }); + + it('should call plugins with default options when plugin options is TRUE', function() { + var plugin = {id: 'a', hook: function() {}, defaults: {a: 42}}; + + Chart.register(plugin); + + var chart = window.acquireChart({ + options: { + plugins: { + a: true + } + } + }); + + spyOn(plugin, 'hook'); + + chart.notifyPlugins('hook'); + + expect(plugin.hook).toHaveBeenCalled(); + expect(plugin.hook.calls.first().args[2]).toEqual({a: 42}); + + Chart.unregister(plugin); + }); - it('should call plugins with default options if plugin config options is undefined', function() { - var plugin = {id: 'a', hook: function() {}, defaults: {a: 'foobar'}}; - - Chart.register(plugin); - spyOn(plugin, 'hook'); - - var chart = window.acquireChart(); + it('should call plugins with default options if plugin config options is undefined', function() { + var plugin = {id: 'a', hook: function() {}, defaults: {a: 'foobar'}}; + + Chart.register(plugin); + spyOn(plugin, 'hook'); + + var chart = window.acquireChart(); - chart.notifyPlugins('hook'); - - expect(plugin.hook).toHaveBeenCalled(); - expect(plugin.hook.calls.first().args[2]).toEqual({a: 'foobar'}); + chart.notifyPlugins('hook'); + + expect(plugin.hook).toHaveBeenCalled(); + expect(plugin.hook.calls.first().args[2]).toEqual({a: 'foobar'}); - Chart.unregister(plugin); - }); + Chart.unregister(plugin); + }); - // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - it('should update plugin options', function() { - var plugin = {id: 'a', hook: function() {}}; - var chart = window.acquireChart({ - plugins: [plugin], - options: { - plugins: { - a: { - foo: 'foo' - } - } - }, - }); + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + it('should update plugin options', function() { + var plugin = {id: 'a', hook: function() {}}; + var chart = window.acquireChart({ + plugins: [plugin], + options: { + plugins: { + a: { + foo: 'foo' + } + } + }, + }); - spyOn(plugin, 'hook'); + spyOn(plugin, 'hook'); - chart.notifyPlugins('hook'); + chart.notifyPlugins('hook'); - expect(plugin.hook).toHaveBeenCalled(); - expect(plugin.hook.calls.first().args[2]).toEqual({foo: 'foo'}); + expect(plugin.hook).toHaveBeenCalled(); + expect(plugin.hook.calls.first().args[2]).toEqual({foo: 'foo'}); - chart.options.plugins.a = {bar: 'bar'}; - chart.update(); + chart.options.plugins.a = {bar: 'bar'}; + chart.update(); - plugin.hook.calls.reset(); - chart.notifyPlugins('hook'); + plugin.hook.calls.reset(); + chart.notifyPlugins('hook'); - expect(plugin.hook).toHaveBeenCalled(); - expect(plugin.hook.calls.first().args[2]).toEqual({bar: 'bar'}); - }); + expect(plugin.hook).toHaveBeenCalled(); + expect(plugin.hook.calls.first().args[2]).toEqual({bar: 'bar'}); + }); - it('should disable all plugins', function() { - var plugin = {id: 'a', hook: function() {}}; - var chart = window.acquireChart({ - plugins: [plugin], - options: { - plugins: false - } - }); + it('should disable all plugins', function() { + var plugin = {id: 'a', hook: function() {}}; + var chart = window.acquireChart({ + plugins: [plugin], + options: { + plugins: false + } + }); - spyOn(plugin, 'hook'); + spyOn(plugin, 'hook'); - chart.notifyPlugins('hook'); + chart.notifyPlugins('hook'); - expect(plugin.hook).not.toHaveBeenCalled(); - }); + expect(plugin.hook).not.toHaveBeenCalled(); + }); - it('should not restart plugins when a double register occurs', function() { - var results = []; - var chart = window.acquireChart({ - plugins: [{ - start: function() { - results.push(1); - } - }] - }); + it('should not restart plugins when a double register occurs', function() { + var results = []; + var chart = window.acquireChart({ + plugins: [{ + start: function() { + results.push(1); + } + }] + }); - Chart.register({id: 'abc', hook: function() {}}); - Chart.register({id: 'def', hook: function() {}}); + Chart.register({id: 'abc', hook: function() {}}); + Chart.register({id: 'def', hook: function() {}}); - chart.update(); + chart.update(); - // The plugin on the chart should only be started once - expect(results).toEqual([1]); - }); - }); + // The plugin on the chart should only be started once + expect(results).toEqual([1]); + }); + }); }); diff --git a/test/specs/core.registry.tests.js b/test/specs/core.registry.tests.js index 05540390e38..ade0ffdb33a 100644 --- a/test/specs/core.registry.tests.js +++ b/test/specs/core.registry.tests.js @@ -1,260 +1,260 @@ describe('Chart.registry', function() { - it('should handle an ES6 controller extension', function() { - class CustomController extends Chart.DatasetController {} - CustomController.id = 'custom'; - CustomController.defaults = { - foo: 'bar' - }; - Chart.register(CustomController); - - expect(Chart.registry.getController('custom')).toEqual(CustomController); - expect(Chart.defaults.controllers.custom).toEqual(CustomController.defaults); - - Chart.unregister(CustomController); - - expect(function() { - Chart.registry.getController('custom'); - }).toThrow(new Error('"custom" is not a registered controller.')); - expect(Chart.defaults.controllers.custom).not.toBeDefined(); - }); - - it('should handle an ES6 scale extension', function() { - class CustomScale extends Chart.Scale {} - CustomScale.id = 'es6Scale'; - CustomScale.defaults = { - foo: 'bar' - }; - Chart.register(CustomScale); - - expect(Chart.registry.getScale('es6Scale')).toEqual(CustomScale); - expect(Chart.defaults.scales.es6Scale).toEqual(CustomScale.defaults); - - Chart.unregister(CustomScale); - - expect(function() { - Chart.registry.getScale('es6Scale'); - }).toThrow(new Error('"es6Scale" is not a registered scale.')); - expect(Chart.defaults.controllers.custom).not.toBeDefined(); - }); - - it('should handle an ES6 element extension', function() { - class CustomElement extends Chart.Element {} - CustomElement.id = 'es6element'; - CustomElement.defaults = { - foo: 'bar' - }; - Chart.register(CustomElement); - - expect(Chart.registry.getElement('es6element')).toEqual(CustomElement); - expect(Chart.defaults.elements.es6element).toEqual(CustomElement.defaults); - - Chart.unregister(CustomElement); - - expect(function() { - Chart.registry.getElement('es6element'); - }).toThrow(new Error('"es6element" is not a registered element.')); - expect(Chart.defaults.elements.es6element).not.toBeDefined(); - }); - - it('should handle an ES6 plugin', function() { - class CustomPlugin {} - CustomPlugin.id = 'es6plugin'; - CustomPlugin.defaults = { - foo: 'bar' - }; - Chart.register(CustomPlugin); - - expect(Chart.registry.getPlugin('es6plugin')).toEqual(CustomPlugin); - expect(Chart.defaults.plugins.es6plugin).toEqual(CustomPlugin.defaults); - - Chart.unregister(CustomPlugin); - - expect(function() { - Chart.registry.getPlugin('es6plugin'); - }).toThrow(new Error('"es6plugin" is not a registered plugin.')); - expect(Chart.defaults.plugins.es6plugin).not.toBeDefined(); - }); - - it('should not accept an object without id', function() { - expect(function() { - Chart.register({foo: 'bar'}); - }).toThrow(new Error('class does not have id: bar')); - - class FaultyPlugin {} - - expect(function() { - Chart.register(FaultyPlugin); - }).toThrow(new Error('class does not have id: class FaultyPlugin {}')); - }); - - it('should not fail when unregistering an object that is not registered', function() { - expect(function() { - Chart.unregister({id: 'foo'}); - }).not.toThrow(); - }); - - describe('Should allow registering explicitly', function() { - class customExtension {} - customExtension.id = 'custom'; - customExtension.defaults = { - prop: true - }; - - it('as controller', function() { - Chart.registry.addControllers(customExtension); - - expect(Chart.registry.getController('custom')).toEqual(customExtension); - expect(Chart.defaults.controllers.custom).toEqual(customExtension.defaults); - - Chart.registry.removeControllers(customExtension); - - expect(function() { - Chart.registry.getController('custom'); - }).toThrow(new Error('"custom" is not a registered controller.')); - expect(Chart.defaults.controllers.custom).not.toBeDefined(); - }); - - it('as scale', function() { - Chart.registry.addScales(customExtension); - - expect(Chart.registry.getScale('custom')).toEqual(customExtension); - expect(Chart.defaults.scales.custom).toEqual(customExtension.defaults); - - Chart.registry.removeScales(customExtension); - - expect(function() { - Chart.registry.getScale('custom'); - }).toThrow(new Error('"custom" is not a registered scale.')); - expect(Chart.defaults.scales.custom).not.toBeDefined(); - }); - - it('as element', function() { - Chart.registry.addElements(customExtension); - - expect(Chart.registry.getElement('custom')).toEqual(customExtension); - expect(Chart.defaults.elements.custom).toEqual(customExtension.defaults); - - Chart.registry.removeElements(customExtension); - - expect(function() { - Chart.registry.getElement('custom'); - }).toThrow(new Error('"custom" is not a registered element.')); - expect(Chart.defaults.elements.custom).not.toBeDefined(); - }); - - it('as plugin', function() { - Chart.registry.addPlugins(customExtension); - - expect(Chart.registry.getPlugin('custom')).toEqual(customExtension); - expect(Chart.defaults.plugins.custom).toEqual(customExtension.defaults); - - Chart.registry.removePlugins(customExtension); - - expect(function() { - Chart.registry.getPlugin('custom'); - }).toThrow(new Error('"custom" is not a registered plugin.')); - expect(Chart.defaults.plugins.custom).not.toBeDefined(); - }); - }); - - it('should fire before/after callbacks', function() { - let beforeRegisterCount = 0; - let afterRegisterCount = 0; - let beforeUnregisterCount = 0; - let afterUnregisterCount = 0; - class custom {} - custom.id = 'custom'; - custom.beforeRegister = () => beforeRegisterCount++; - custom.afterRegister = () => afterRegisterCount++; - custom.beforeUnregister = () => beforeUnregisterCount++; - custom.afterUnregister = () => afterUnregisterCount++; - - Chart.registry.addControllers(custom); - expect(beforeRegisterCount).withContext('beforeRegister').toBe(1); - expect(afterRegisterCount).withContext('afterRegister').toBe(1); - Chart.registry.removeControllers(custom); - expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(1); - expect(afterUnregisterCount).withContext('afterUnregister').toBe(1); - - Chart.registry.addScales(custom); - expect(beforeRegisterCount).withContext('beforeRegister').toBe(2); - expect(afterRegisterCount).withContext('afterRegister').toBe(2); - Chart.registry.removeScales(custom); - expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(2); - expect(afterUnregisterCount).withContext('afterUnregister').toBe(2); - - Chart.registry.addElements(custom); - expect(beforeRegisterCount).withContext('beforeRegister').toBe(3); - expect(afterRegisterCount).withContext('afterRegister').toBe(3); - Chart.registry.removeElements(custom); - expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(3); - expect(afterUnregisterCount).withContext('afterUnregister').toBe(3); - - Chart.register(custom); - expect(beforeRegisterCount).withContext('beforeRegister').toBe(4); - expect(afterRegisterCount).withContext('afterRegister').toBe(4); - Chart.unregister(custom); - expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(4); - expect(afterUnregisterCount).withContext('afterUnregister').toBe(4); - }); - - it('should preserve existing defaults', function() { - Chart.defaults.controllers.test = {test1: true}; - - class testController extends Chart.DatasetController {} - testController.id = 'test'; - testController.defaults = {test1: false, test2: true}; - - Chart.register(testController); - expect(Chart.defaults.controllers.test).toEqual({test1: true, test2: true}); - - Chart.unregister(testController); - expect(Chart.defaults.controllers.test).not.toBeDefined(); - }); - - describe('should handle multiple items', function() { - class test1 extends Chart.DatasetController {} - test1.id = 'test1'; - class test2 extends Chart.Scale {} - test2.id = 'test2'; - - it('separately', function() { - Chart.register(test1, test2); - expect(Chart.registry.getController('test1')).toEqual(test1); - expect(Chart.registry.getScale('test2')).toEqual(test2); - Chart.unregister(test1, test2); - expect(function() { - Chart.registry.getController('test1'); - }).toThrow(); - expect(function() { - Chart.registry.getScale('test2'); - }).toThrow(); - }); - - it('as array', function() { - Chart.register([test1, test2]); - expect(Chart.registry.getController('test1')).toEqual(test1); - expect(Chart.registry.getScale('test2')).toEqual(test2); - Chart.unregister([test1, test2]); - expect(function() { - Chart.registry.getController('test1'); - }).toThrow(); - expect(function() { - Chart.registry.getScale('test2'); - }).toThrow(); - }); - - it('as object', function() { - Chart.register({test1, test2}); - expect(Chart.registry.getController('test1')).toEqual(test1); - expect(Chart.registry.getScale('test2')).toEqual(test2); - Chart.unregister({test1, test2}); - expect(function() { - Chart.registry.getController('test1'); - }).toThrow(); - expect(function() { - Chart.registry.getScale('test2'); - }).toThrow(); - }); - }); + it('should handle an ES6 controller extension', function() { + class CustomController extends Chart.DatasetController {} + CustomController.id = 'custom'; + CustomController.defaults = { + foo: 'bar' + }; + Chart.register(CustomController); + + expect(Chart.registry.getController('custom')).toEqual(CustomController); + expect(Chart.defaults.controllers.custom).toEqual(CustomController.defaults); + + Chart.unregister(CustomController); + + expect(function() { + Chart.registry.getController('custom'); + }).toThrow(new Error('"custom" is not a registered controller.')); + expect(Chart.defaults.controllers.custom).not.toBeDefined(); + }); + + it('should handle an ES6 scale extension', function() { + class CustomScale extends Chart.Scale {} + CustomScale.id = 'es6Scale'; + CustomScale.defaults = { + foo: 'bar' + }; + Chart.register(CustomScale); + + expect(Chart.registry.getScale('es6Scale')).toEqual(CustomScale); + expect(Chart.defaults.scales.es6Scale).toEqual(CustomScale.defaults); + + Chart.unregister(CustomScale); + + expect(function() { + Chart.registry.getScale('es6Scale'); + }).toThrow(new Error('"es6Scale" is not a registered scale.')); + expect(Chart.defaults.controllers.custom).not.toBeDefined(); + }); + + it('should handle an ES6 element extension', function() { + class CustomElement extends Chart.Element {} + CustomElement.id = 'es6element'; + CustomElement.defaults = { + foo: 'bar' + }; + Chart.register(CustomElement); + + expect(Chart.registry.getElement('es6element')).toEqual(CustomElement); + expect(Chart.defaults.elements.es6element).toEqual(CustomElement.defaults); + + Chart.unregister(CustomElement); + + expect(function() { + Chart.registry.getElement('es6element'); + }).toThrow(new Error('"es6element" is not a registered element.')); + expect(Chart.defaults.elements.es6element).not.toBeDefined(); + }); + + it('should handle an ES6 plugin', function() { + class CustomPlugin {} + CustomPlugin.id = 'es6plugin'; + CustomPlugin.defaults = { + foo: 'bar' + }; + Chart.register(CustomPlugin); + + expect(Chart.registry.getPlugin('es6plugin')).toEqual(CustomPlugin); + expect(Chart.defaults.plugins.es6plugin).toEqual(CustomPlugin.defaults); + + Chart.unregister(CustomPlugin); + + expect(function() { + Chart.registry.getPlugin('es6plugin'); + }).toThrow(new Error('"es6plugin" is not a registered plugin.')); + expect(Chart.defaults.plugins.es6plugin).not.toBeDefined(); + }); + + it('should not accept an object without id', function() { + expect(function() { + Chart.register({foo: 'bar'}); + }).toThrow(new Error('class does not have id: bar')); + + class FaultyPlugin {} + + expect(function() { + Chart.register(FaultyPlugin); + }).toThrow(new Error('class does not have id: class FaultyPlugin {}')); + }); + + it('should not fail when unregistering an object that is not registered', function() { + expect(function() { + Chart.unregister({id: 'foo'}); + }).not.toThrow(); + }); + + describe('Should allow registering explicitly', function() { + class customExtension {} + customExtension.id = 'custom'; + customExtension.defaults = { + prop: true + }; + + it('as controller', function() { + Chart.registry.addControllers(customExtension); + + expect(Chart.registry.getController('custom')).toEqual(customExtension); + expect(Chart.defaults.controllers.custom).toEqual(customExtension.defaults); + + Chart.registry.removeControllers(customExtension); + + expect(function() { + Chart.registry.getController('custom'); + }).toThrow(new Error('"custom" is not a registered controller.')); + expect(Chart.defaults.controllers.custom).not.toBeDefined(); + }); + + it('as scale', function() { + Chart.registry.addScales(customExtension); + + expect(Chart.registry.getScale('custom')).toEqual(customExtension); + expect(Chart.defaults.scales.custom).toEqual(customExtension.defaults); + + Chart.registry.removeScales(customExtension); + + expect(function() { + Chart.registry.getScale('custom'); + }).toThrow(new Error('"custom" is not a registered scale.')); + expect(Chart.defaults.scales.custom).not.toBeDefined(); + }); + + it('as element', function() { + Chart.registry.addElements(customExtension); + + expect(Chart.registry.getElement('custom')).toEqual(customExtension); + expect(Chart.defaults.elements.custom).toEqual(customExtension.defaults); + + Chart.registry.removeElements(customExtension); + + expect(function() { + Chart.registry.getElement('custom'); + }).toThrow(new Error('"custom" is not a registered element.')); + expect(Chart.defaults.elements.custom).not.toBeDefined(); + }); + + it('as plugin', function() { + Chart.registry.addPlugins(customExtension); + + expect(Chart.registry.getPlugin('custom')).toEqual(customExtension); + expect(Chart.defaults.plugins.custom).toEqual(customExtension.defaults); + + Chart.registry.removePlugins(customExtension); + + expect(function() { + Chart.registry.getPlugin('custom'); + }).toThrow(new Error('"custom" is not a registered plugin.')); + expect(Chart.defaults.plugins.custom).not.toBeDefined(); + }); + }); + + it('should fire before/after callbacks', function() { + let beforeRegisterCount = 0; + let afterRegisterCount = 0; + let beforeUnregisterCount = 0; + let afterUnregisterCount = 0; + class custom {} + custom.id = 'custom'; + custom.beforeRegister = () => beforeRegisterCount++; + custom.afterRegister = () => afterRegisterCount++; + custom.beforeUnregister = () => beforeUnregisterCount++; + custom.afterUnregister = () => afterUnregisterCount++; + + Chart.registry.addControllers(custom); + expect(beforeRegisterCount).withContext('beforeRegister').toBe(1); + expect(afterRegisterCount).withContext('afterRegister').toBe(1); + Chart.registry.removeControllers(custom); + expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(1); + expect(afterUnregisterCount).withContext('afterUnregister').toBe(1); + + Chart.registry.addScales(custom); + expect(beforeRegisterCount).withContext('beforeRegister').toBe(2); + expect(afterRegisterCount).withContext('afterRegister').toBe(2); + Chart.registry.removeScales(custom); + expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(2); + expect(afterUnregisterCount).withContext('afterUnregister').toBe(2); + + Chart.registry.addElements(custom); + expect(beforeRegisterCount).withContext('beforeRegister').toBe(3); + expect(afterRegisterCount).withContext('afterRegister').toBe(3); + Chart.registry.removeElements(custom); + expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(3); + expect(afterUnregisterCount).withContext('afterUnregister').toBe(3); + + Chart.register(custom); + expect(beforeRegisterCount).withContext('beforeRegister').toBe(4); + expect(afterRegisterCount).withContext('afterRegister').toBe(4); + Chart.unregister(custom); + expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(4); + expect(afterUnregisterCount).withContext('afterUnregister').toBe(4); + }); + + it('should preserve existing defaults', function() { + Chart.defaults.controllers.test = {test1: true}; + + class testController extends Chart.DatasetController {} + testController.id = 'test'; + testController.defaults = {test1: false, test2: true}; + + Chart.register(testController); + expect(Chart.defaults.controllers.test).toEqual({test1: true, test2: true}); + + Chart.unregister(testController); + expect(Chart.defaults.controllers.test).not.toBeDefined(); + }); + + describe('should handle multiple items', function() { + class test1 extends Chart.DatasetController {} + test1.id = 'test1'; + class test2 extends Chart.Scale {} + test2.id = 'test2'; + + it('separately', function() { + Chart.register(test1, test2); + expect(Chart.registry.getController('test1')).toEqual(test1); + expect(Chart.registry.getScale('test2')).toEqual(test2); + Chart.unregister(test1, test2); + expect(function() { + Chart.registry.getController('test1'); + }).toThrow(); + expect(function() { + Chart.registry.getScale('test2'); + }).toThrow(); + }); + + it('as array', function() { + Chart.register([test1, test2]); + expect(Chart.registry.getController('test1')).toEqual(test1); + expect(Chart.registry.getScale('test2')).toEqual(test2); + Chart.unregister([test1, test2]); + expect(function() { + Chart.registry.getController('test1'); + }).toThrow(); + expect(function() { + Chart.registry.getScale('test2'); + }).toThrow(); + }); + + it('as object', function() { + Chart.register({test1, test2}); + expect(Chart.registry.getController('test1')).toEqual(test1); + expect(Chart.registry.getScale('test2')).toEqual(test2); + Chart.unregister({test1, test2}); + expect(function() { + Chart.registry.getController('test1'); + }).toThrow(); + expect(function() { + Chart.registry.getScale('test2'); + }).toThrow(); + }); + }); }); diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index e4b8fb82241..58560745d50 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -1,597 +1,597 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Core.scale', function() { - describe('auto', jasmine.fixture.specs('core.scale')); - - it('should provide default scale label options', function() { - expect(Chart.defaults.scale.scaleLabel).toEqual({ - color: Chart.defaults.color, - display: false, - labelString: '', - padding: { - top: 4, - bottom: 4 - } - }); - }); - - describe('displaying xAxis ticks with autoSkip=true', function() { - function getChart(data) { - return window.acquireChart({ - type: 'line', - data: data, - options: { - scales: { - x: { - ticks: { - autoSkip: true - } - } - } - } - }); - } - - function lastTick(chart) { - var xAxis = chart.scales.x; - var ticks = xAxis.getTicks(); - return ticks[ticks.length - 1]; - } - - it('should display the last tick if it fits evenly with other ticks', function() { - var chart = getChart({ - labels: [ - 'January 2018', 'February 2018', 'March 2018', 'April 2018', - 'May 2018', 'June 2018', 'July 2018', 'August 2018', - 'September 2018' - ], - datasets: [{ - data: [12, 19, 3, 5, 2, 3, 7, 8, 9] - }] - }); - - expect(lastTick(chart).label).toEqual('September 2018'); - }); - - it('should not display the last tick if it does not fit evenly', function() { - var chart = getChart({ - labels: [ - 'January 2018', 'February 2018', 'March 2018', 'April 2018', - 'May 2018', 'June 2018', 'July 2018', 'August 2018', - 'September 2018', 'October 2018', 'November 2018', 'December 2018', - 'January 2019', 'February 2019', 'March 2019', 'April 2019', - 'May 2019', 'June 2019', 'July 2019', 'August 2019', - 'September 2019', 'October 2019', 'November 2019', 'December 2019', - 'January 2020', 'February 2020' - ], - datasets: [{ - data: [12, 19, 3, 5, 2, 3, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] - }] - }); - - expect(lastTick(chart).label).toEqual('January 2020'); - }); - }); - - 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, 153.5, 256.5, 358.5, 460.5] - }, { - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], - offsetGridLines: true, - offset: false, - expected: [64.5, 192.5, 320.5, 448.5] - }, { - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], - offsetGridLines: true, - offset: true, - expected: [0.5, 102.5, 204.5, 307.5, 409.5, 512.5] - }, { - 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: [512.5] - }, { - labels: ['tick1'], - offsetGridLines: true, - offset: true, - expected: [0.5, 512.5] - }]; - - gridLineTests.forEach(function(test) { - it('should get the correct pixels for 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: { - x: { - gridLines: { - offsetGridLines: test.offsetGridLines, - drawTicks: false - }, - ticks: { - display: false - }, - offset: test.offset - }, - y: { - display: false - } - }, - plugins: { - legend: false - } - } - }); - - var xScale = chart.scales.x; - 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 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: { - x: { - display: false - }, - y: { - type: 'category', - gridLines: { - offsetGridLines: test.offsetGridLines, - drawTicks: false - }, - ticks: { - display: false - }, - offset: test.offset - } - }, - plugins: { - legend: false - } - } - }); - - var yScale = chart.scales.y; - yScale.ctx = window.createMockContext(); - chart.draw(); - - expect(yScale.ctx.getCalls().filter(function(x) { - return x.name === 'moveTo' && x.args[0] === 1; - }).map(function(x) { - return x.args[1]; - })).toEqual(test.expected); - }); - }); - - it('should add the correct padding for long tick labels', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: [ - 'This is a very long label', - 'This is a very long label' - ], - datasets: [{ - data: [0, 1] - }] - }, - options: { - scales: { - y: { - display: false - } - }, - plugins: { - legend: false - } - } - }, { - canvas: { - height: 100, - width: 200 - } - }); - - var scale = chart.scales.x; - expect(scale.left).toBeGreaterThan(100); - expect(scale.right).toBeGreaterThan(190); - }); - - describe('given the axes display option is set to auto', function() { - describe('for the x axes', function() { - it('should draw the axes if at least one associated dataset is visible', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [100, 200, 100, 50], - xAxisId: 'foo', - hidden: true, - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }, { - data: [100, 200, 100, 50], - xAxisId: 'foo', - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }] - }, - options: { - scales: { - x: { - display: 'auto' - }, - y: { - type: 'category', - } - } - } - }); - - var scale = chart.scales.x; - scale.ctx = window.createMockContext(); - chart.draw(); - - expect(scale.ctx.getCalls().length).toBeGreaterThan(0); - expect(scale.height).toBeGreaterThan(0); - }); - - it('should not draw the axes if no associated datasets are visible', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [100, 200, 100, 50], - xAxisId: 'foo', - hidden: true, - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }] - }, - options: { - scales: { - x: { - display: 'auto' - } - } - } - }); - - var scale = chart.scales.x; - scale.ctx = window.createMockContext(); - chart.draw(); - - expect(scale.ctx.getCalls().length).toBe(0); - expect(scale.height).toBe(0); - }); - }); - - describe('for the y axes', function() { - it('should draw the axes if at least one associated dataset is visible', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [100, 200, 100, 50], - yAxisId: 'foo', - hidden: true, - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }, { - data: [100, 200, 100, 50], - yAxisId: 'foo', - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }] - }, - options: { - scales: { - y: { - display: 'auto' - } - } - } - }); - - var scale = chart.scales.y; - scale.ctx = window.createMockContext(); - chart.draw(); - - expect(scale.ctx.getCalls().length).toBeGreaterThan(0); - expect(scale.width).toBeGreaterThan(0); - }); - - it('should not draw the axes if no associated datasets are visible', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [100, 200, 100, 50], - yAxisId: 'foo', - hidden: true, - labels: ['Q1', 'Q2', 'Q3', 'Q4'] - }] - }, - options: { - scales: { - y: { - display: 'auto' - } - } - } - }); - - var scale = chart.scales.y; - scale.ctx = window.createMockContext(); - chart.draw(); - - expect(scale.ctx.getCalls().length).toBe(0); - expect(scale.width).toBe(0); - }); - }); - }); - - describe('afterBuildTicks', function() { - it('should allow filtering of ticks', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'category', - labels: labels, - afterBuildTicks: function(scale) { - scale.ticks = scale.ticks.slice(1); - } - } - } - } - }); - - var scale = chart.scales.x; - expect(getLabels(scale)).toEqual(labels.slice(1)); - }); - - it('should allow no return value from callback', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'category', - labels: labels, - afterBuildTicks: function() { } - } - } - } - }); - - var scale = chart.scales.x; - expect(getLabels(scale)).toEqual(labels); - }); - - it('should allow empty ticks', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'category', - labels: labels, - afterBuildTicks: function(scale) { - scale.ticks = []; - } - } - } - } - }); - - var scale = chart.scales.x; - expect(scale.ticks.length).toBe(0); - }); - }); - - describe('_layers', function() { - it('should default to one layer', function() { - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'linear', - } - } - } - }); - - var scale = chart.scales.x; - expect(scale._layers().length).toEqual(1); - }); - - it('should default to one layer for custom scales', function() { - class CustomScale extends Chart.Scale { - draw() {} - convertTicksToLabels() { - return ['tick']; - } - } - CustomScale.id = 'customScale'; - CustomScale.defaults = {}; - Chart.register(CustomScale); - - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'customScale', - gridLines: { - z: 10 - }, - ticks: { - z: 20 - } - } - } - } - }); - - var scale = chart.scales.x; - expect(scale._layers().length).toEqual(1); - expect(scale._layers()[0].z).toEqual(20); - }); - - it('should default to one layer when z is equal between ticks and grid', function() { - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'linear', - ticks: { - z: 10 - }, - gridLines: { - z: 10 - } - } - } - } - }); - - var scale = chart.scales.x; - expect(scale._layers().length).toEqual(1); - }); - - - it('should return 2 layers when z is not equal between ticks and grid', function() { - var chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'linear', - ticks: { - z: 10 - } - } - } - } - }); - - expect(chart.scales.x._layers().length).toEqual(2); - - chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'linear', - gridLines: { - z: 11 - } - } - } - } - }); - - expect(chart.scales.x._layers().length).toEqual(2); - - chart = window.acquireChart({ - type: 'line', - options: { - scales: { - x: { - type: 'linear', - ticks: { - z: 10 - }, - gridLines: { - z: 11 - } - } - } - } - }); - - expect(chart.scales.x._layers().length).toEqual(2); - - }); - - }); - - describe('min and max', function() { - it('should be limited to visible data', function() { - var chart = window.acquireChart({ - type: 'scatter', - data: { - datasets: [{ - data: [{x: 100, y: 100}, {x: -100, y: -100}] - }, { - data: [{x: 10, y: 10}, {x: -10, y: -10}] - }] - }, - options: { - scales: { - x: { - id: 'x', - type: 'linear', - min: -20, - max: 20 - }, - y: { - id: 'y', - type: 'linear' - } - } - } - }); - - expect(chart.scales.x.min).toEqual(-20); - expect(chart.scales.x.max).toEqual(20); - expect(chart.scales.y.min).toEqual(-10); - expect(chart.scales.y.max).toEqual(10); - }); - }); + describe('auto', jasmine.fixture.specs('core.scale')); + + it('should provide default scale label options', function() { + expect(Chart.defaults.scale.scaleLabel).toEqual({ + color: Chart.defaults.color, + display: false, + labelString: '', + padding: { + top: 4, + bottom: 4 + } + }); + }); + + describe('displaying xAxis ticks with autoSkip=true', function() { + function getChart(data) { + return window.acquireChart({ + type: 'line', + data: data, + options: { + scales: { + x: { + ticks: { + autoSkip: true + } + } + } + } + }); + } + + function lastTick(chart) { + var xAxis = chart.scales.x; + var ticks = xAxis.getTicks(); + return ticks[ticks.length - 1]; + } + + it('should display the last tick if it fits evenly with other ticks', function() { + var chart = getChart({ + labels: [ + 'January 2018', 'February 2018', 'March 2018', 'April 2018', + 'May 2018', 'June 2018', 'July 2018', 'August 2018', + 'September 2018' + ], + datasets: [{ + data: [12, 19, 3, 5, 2, 3, 7, 8, 9] + }] + }); + + expect(lastTick(chart).label).toEqual('September 2018'); + }); + + it('should not display the last tick if it does not fit evenly', function() { + var chart = getChart({ + labels: [ + 'January 2018', 'February 2018', 'March 2018', 'April 2018', + 'May 2018', 'June 2018', 'July 2018', 'August 2018', + 'September 2018', 'October 2018', 'November 2018', 'December 2018', + 'January 2019', 'February 2019', 'March 2019', 'April 2019', + 'May 2019', 'June 2019', 'July 2019', 'August 2019', + 'September 2019', 'October 2019', 'November 2019', 'December 2019', + 'January 2020', 'February 2020' + ], + datasets: [{ + data: [12, 19, 3, 5, 2, 3, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + }] + }); + + expect(lastTick(chart).label).toEqual('January 2020'); + }); + }); + + 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, 153.5, 256.5, 358.5, 460.5] + }, { + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: true, + offset: false, + expected: [64.5, 192.5, 320.5, 448.5] + }, { + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: true, + offset: true, + expected: [0.5, 102.5, 204.5, 307.5, 409.5, 512.5] + }, { + 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: [512.5] + }, { + labels: ['tick1'], + offsetGridLines: true, + offset: true, + expected: [0.5, 512.5] + }]; + + gridLineTests.forEach(function(test) { + it('should get the correct pixels for 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: { + x: { + gridLines: { + offsetGridLines: test.offsetGridLines, + drawTicks: false + }, + ticks: { + display: false + }, + offset: test.offset + }, + y: { + display: false + } + }, + plugins: { + legend: false + } + } + }); + + var xScale = chart.scales.x; + 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 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: { + x: { + display: false + }, + y: { + type: 'category', + gridLines: { + offsetGridLines: test.offsetGridLines, + drawTicks: false + }, + ticks: { + display: false + }, + offset: test.offset + } + }, + plugins: { + legend: false + } + } + }); + + var yScale = chart.scales.y; + yScale.ctx = window.createMockContext(); + chart.draw(); + + expect(yScale.ctx.getCalls().filter(function(x) { + return x.name === 'moveTo' && x.args[0] === 1; + }).map(function(x) { + return x.args[1]; + })).toEqual(test.expected); + }); + }); + + it('should add the correct padding for long tick labels', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: [ + 'This is a very long label', + 'This is a very long label' + ], + datasets: [{ + data: [0, 1] + }] + }, + options: { + scales: { + y: { + display: false + } + }, + plugins: { + legend: false + } + } + }, { + canvas: { + height: 100, + width: 200 + } + }); + + var scale = chart.scales.x; + expect(scale.left).toBeGreaterThan(100); + expect(scale.right).toBeGreaterThan(190); + }); + + describe('given the axes display option is set to auto', function() { + describe('for the x axes', function() { + it('should draw the axes if at least one associated dataset is visible', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [100, 200, 100, 50], + xAxisId: 'foo', + hidden: true, + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }, { + data: [100, 200, 100, 50], + xAxisId: 'foo', + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }] + }, + options: { + scales: { + x: { + display: 'auto' + }, + y: { + type: 'category', + } + } + } + }); + + var scale = chart.scales.x; + scale.ctx = window.createMockContext(); + chart.draw(); + + expect(scale.ctx.getCalls().length).toBeGreaterThan(0); + expect(scale.height).toBeGreaterThan(0); + }); + + it('should not draw the axes if no associated datasets are visible', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [100, 200, 100, 50], + xAxisId: 'foo', + hidden: true, + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }] + }, + options: { + scales: { + x: { + display: 'auto' + } + } + } + }); + + var scale = chart.scales.x; + scale.ctx = window.createMockContext(); + chart.draw(); + + expect(scale.ctx.getCalls().length).toBe(0); + expect(scale.height).toBe(0); + }); + }); + + describe('for the y axes', function() { + it('should draw the axes if at least one associated dataset is visible', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [100, 200, 100, 50], + yAxisId: 'foo', + hidden: true, + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }, { + data: [100, 200, 100, 50], + yAxisId: 'foo', + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }] + }, + options: { + scales: { + y: { + display: 'auto' + } + } + } + }); + + var scale = chart.scales.y; + scale.ctx = window.createMockContext(); + chart.draw(); + + expect(scale.ctx.getCalls().length).toBeGreaterThan(0); + expect(scale.width).toBeGreaterThan(0); + }); + + it('should not draw the axes if no associated datasets are visible', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [100, 200, 100, 50], + yAxisId: 'foo', + hidden: true, + labels: ['Q1', 'Q2', 'Q3', 'Q4'] + }] + }, + options: { + scales: { + y: { + display: 'auto' + } + } + } + }); + + var scale = chart.scales.y; + scale.ctx = window.createMockContext(); + chart.draw(); + + expect(scale.ctx.getCalls().length).toBe(0); + expect(scale.width).toBe(0); + }); + }); + }); + + describe('afterBuildTicks', function() { + it('should allow filtering of ticks', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'category', + labels: labels, + afterBuildTicks: function(scale) { + scale.ticks = scale.ticks.slice(1); + } + } + } + } + }); + + var scale = chart.scales.x; + expect(getLabels(scale)).toEqual(labels.slice(1)); + }); + + it('should allow no return value from callback', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'category', + labels: labels, + afterBuildTicks: function() { } + } + } + } + }); + + var scale = chart.scales.x; + expect(getLabels(scale)).toEqual(labels); + }); + + it('should allow empty ticks', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'category', + labels: labels, + afterBuildTicks: function(scale) { + scale.ticks = []; + } + } + } + } + }); + + var scale = chart.scales.x; + expect(scale.ticks.length).toBe(0); + }); + }); + + describe('_layers', function() { + it('should default to one layer', function() { + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'linear', + } + } + } + }); + + var scale = chart.scales.x; + expect(scale._layers().length).toEqual(1); + }); + + it('should default to one layer for custom scales', function() { + class CustomScale extends Chart.Scale { + draw() {} + convertTicksToLabels() { + return ['tick']; + } + } + CustomScale.id = 'customScale'; + CustomScale.defaults = {}; + Chart.register(CustomScale); + + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'customScale', + gridLines: { + z: 10 + }, + ticks: { + z: 20 + } + } + } + } + }); + + var scale = chart.scales.x; + expect(scale._layers().length).toEqual(1); + expect(scale._layers()[0].z).toEqual(20); + }); + + it('should default to one layer when z is equal between ticks and grid', function() { + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'linear', + ticks: { + z: 10 + }, + gridLines: { + z: 10 + } + } + } + } + }); + + var scale = chart.scales.x; + expect(scale._layers().length).toEqual(1); + }); + + + it('should return 2 layers when z is not equal between ticks and grid', function() { + var chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'linear', + ticks: { + z: 10 + } + } + } + } + }); + + expect(chart.scales.x._layers().length).toEqual(2); + + chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'linear', + gridLines: { + z: 11 + } + } + } + } + }); + + expect(chart.scales.x._layers().length).toEqual(2); + + chart = window.acquireChart({ + type: 'line', + options: { + scales: { + x: { + type: 'linear', + ticks: { + z: 10 + }, + gridLines: { + z: 11 + } + } + } + } + }); + + expect(chart.scales.x._layers().length).toEqual(2); + + }); + + }); + + describe('min and max', function() { + it('should be limited to visible data', function() { + var chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{x: 100, y: 100}, {x: -100, y: -100}] + }, { + data: [{x: 10, y: 10}, {x: -10, y: -10}] + }] + }, + options: { + scales: { + x: { + id: 'x', + type: 'linear', + min: -20, + max: 20 + }, + y: { + id: 'y', + type: 'linear' + } + } + } + }); + + expect(chart.scales.x.min).toEqual(-20); + expect(chart.scales.x.max).toEqual(20); + expect(chart.scales.y.min).toEqual(-10); + expect(chart.scales.y.max).toEqual(10); + }); + }); }); diff --git a/test/specs/core.ticks.tests.js b/test/specs/core.ticks.tests.js index 4858c4b8039..149d19e72e6 100644 --- a/test/specs/core.ticks.tests.js +++ b/test/specs/core.ticks.tests.js @@ -1,99 +1,99 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Test tick generators', function() { - // formatters are used as default config values so users want to be able to reference them - it('Should expose formatters api', function() { - expect(typeof Chart.Ticks).toBeDefined(); - expect(typeof Chart.Ticks.formatters).toBeDefined(); - expect(typeof Chart.Ticks.formatters.values).toBe('function'); - expect(typeof Chart.Ticks.formatters.numeric).toBe('function'); - }); + // formatters are used as default config values so users want to be able to reference them + it('Should expose formatters api', function() { + expect(typeof Chart.Ticks).toBeDefined(); + expect(typeof Chart.Ticks.formatters).toBeDefined(); + expect(typeof Chart.Ticks.formatters.values).toBe('function'); + expect(typeof Chart.Ticks.formatters.numeric).toBe('function'); + }); - it('Should generate linear spaced ticks with correct precision', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [] - }], - }, - options: { - plugins: { - legend: false - }, - scales: { - x: { - type: 'linear', - position: 'bottom', - ticks: { - callback: function(value) { - return value.toString(); - } - } - }, - y: { - type: 'linear', - ticks: { - callback: function(value) { - return value.toString(); - } - } - } - } - } - }); + it('Should generate linear spaced ticks with correct precision', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }], + }, + options: { + plugins: { + legend: false + }, + scales: { + x: { + type: 'linear', + position: 'bottom', + ticks: { + callback: function(value) { + return value.toString(); + } + } + }, + y: { + type: 'linear', + ticks: { + callback: function(value) { + return value.toString(); + } + } + } + } + } + }); - var xLabels = getLabels(chart.scales.x); - var yLabels = getLabels(chart.scales.y); + var xLabels = getLabels(chart.scales.x); + var yLabels = getLabels(chart.scales.y); - expect(xLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); - expect(yLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); - }); + expect(xLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); + expect(yLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); + }); - it('Should generate logarithmic spaced ticks with correct precision', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [] - }], - }, - options: { - plugins: { - legend: false - }, - scales: { - x: { - type: 'logarithmic', - position: 'bottom', - min: 0.1, - max: 1, - ticks: { - callback: function(value) { - return value.toString(); - } - } - }, - y: { - type: 'logarithmic', - min: 0.1, - max: 1, - ticks: { - callback: function(value) { - return value.toString(); - } - } - } - } - } - }); + it('Should generate logarithmic spaced ticks with correct precision', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }], + }, + options: { + plugins: { + legend: false + }, + scales: { + x: { + type: 'logarithmic', + position: 'bottom', + min: 0.1, + max: 1, + ticks: { + callback: function(value) { + return value.toString(); + } + } + }, + y: { + type: 'logarithmic', + min: 0.1, + max: 1, + ticks: { + callback: function(value) { + return value.toString(); + } + } + } + } + } + }); - var xLabels = getLabels(chart.scales.x); - var yLabels = getLabels(chart.scales.y); + var xLabels = getLabels(chart.scales.x); + var yLabels = getLabels(chart.scales.y); - expect(xLabels).toEqual(['0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); - expect(yLabels).toEqual(['0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); - }); + expect(xLabels).toEqual(['0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); + expect(yLabels).toEqual(['0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']); + }); }); diff --git a/test/specs/element.arc.tests.js b/test/specs/element.arc.tests.js index 65c03b9913b..5fe3339deec 100644 --- a/test/specs/element.arc.tests.js +++ b/test/specs/element.arc.tests.js @@ -1,100 +1,100 @@ // Test the rectangle element describe('Arc element tests', function() { - it ('should determine if in range', function() { - // Mock out the arc as if the controller put it there - var arc = new Chart.elements.ArcElement({ - startAngle: 0, - endAngle: Math.PI / 2, - x: 0, - y: 0, - innerRadius: 5, - outerRadius: 10, - }); + it ('should determine if in range', function() { + // Mock out the arc as if the controller put it there + var arc = new Chart.elements.ArcElement({ + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 5, + outerRadius: 10, + }); - expect(arc.inRange(2, 2)).toBe(false); - expect(arc.inRange(7, 0)).toBe(true); - expect(arc.inRange(0, 11)).toBe(false); - expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true); - expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false); - }); + expect(arc.inRange(2, 2)).toBe(false); + expect(arc.inRange(7, 0)).toBe(true); + expect(arc.inRange(0, 11)).toBe(false); + expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true); + expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false); + }); - it ('should determine if in range, when full circle', function() { - // Mock out the arc as if the controller put it there - var arc = new Chart.elements.ArcElement({ - startAngle: -Math.PI, - endAngle: Math.PI * 1.5, - x: 0, - y: 0, - innerRadius: 0, - outerRadius: 10, - circumference: Math.PI * 2 - }); + it ('should determine if in range, when full circle', function() { + // Mock out the arc as if the controller put it there + var arc = new Chart.elements.ArcElement({ + startAngle: -Math.PI, + endAngle: Math.PI * 1.5, + x: 0, + y: 0, + innerRadius: 0, + outerRadius: 10, + circumference: Math.PI * 2 + }); - expect(arc.inRange(7, 7)).toBe(true); - }); + expect(arc.inRange(7, 7)).toBe(true); + }); - it ('should get the tooltip position', function() { - // Mock out the arc as if the controller put it there - var arc = new Chart.elements.ArcElement({ - startAngle: 0, - endAngle: Math.PI / 2, - x: 0, - y: 0, - innerRadius: 0, - outerRadius: Math.sqrt(2), - }); + it ('should get the tooltip position', function() { + // Mock out the arc as if the controller put it there + var arc = new Chart.elements.ArcElement({ + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 0, + outerRadius: Math.sqrt(2), + }); - var pos = arc.tooltipPosition(); - expect(pos.x).toBeCloseTo(0.5); - expect(pos.y).toBeCloseTo(0.5); - }); + var pos = arc.tooltipPosition(); + expect(pos.x).toBeCloseTo(0.5); + expect(pos.y).toBeCloseTo(0.5); + }); - it ('should get the center', function() { - // Mock out the arc as if the controller put it there - var arc = new Chart.elements.ArcElement({ - startAngle: 0, - endAngle: Math.PI / 2, - x: 0, - y: 0, - innerRadius: 0, - outerRadius: Math.sqrt(2), - }); + it ('should get the center', function() { + // Mock out the arc as if the controller put it there + var arc = new Chart.elements.ArcElement({ + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 0, + outerRadius: Math.sqrt(2), + }); - var center = arc.getCenterPoint(); - expect(center.x).toBeCloseTo(0.5, 6); - expect(center.y).toBeCloseTo(0.5, 6); - }); + var center = arc.getCenterPoint(); + expect(center.x).toBeCloseTo(0.5, 6); + expect(center.y).toBeCloseTo(0.5, 6); + }); - it('should not draw when radius < 0', function() { - var ctx = window.createMockContext(); + it('should not draw when radius < 0', function() { + var ctx = window.createMockContext(); - var arc = new Chart.elements.ArcElement({ - startAngle: 0, - endAngle: Math.PI / 2, - x: 0, - y: 0, - innerRadius: -0.1, - outerRadius: Math.sqrt(2), - options: {} - }); + var arc = new Chart.elements.ArcElement({ + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: -0.1, + outerRadius: Math.sqrt(2), + options: {} + }); - arc.draw(ctx); + arc.draw(ctx); - expect(ctx.getCalls().length).toBe(0); + expect(ctx.getCalls().length).toBe(0); - arc = new Chart.elements.ArcElement({ - startAngle: 0, - endAngle: Math.PI / 2, - x: 0, - y: 0, - innerRadius: 0, - outerRadius: -1, - options: {} - }); + arc = new Chart.elements.ArcElement({ + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 0, + outerRadius: -1, + options: {} + }); - arc.draw(ctx); + arc.draw(ctx); - expect(ctx.getCalls().length).toBe(0); - }); + expect(ctx.getCalls().length).toBe(0); + }); }); diff --git a/test/specs/element.bar.tests.js b/test/specs/element.bar.tests.js index 79e4b796738..128bdddc12e 100644 --- a/test/specs/element.bar.tests.js +++ b/test/specs/element.bar.tests.js @@ -1,67 +1,67 @@ // Test the bar element describe('Bar element tests', function() { - it('Should correctly identify as in range', function() { - var bar = new Chart.elements.BarElement({ - base: 0, - width: 4, - x: 10, - y: 15 - }); + it('Should correctly identify as in range', function() { + var bar = new Chart.elements.BarElement({ + base: 0, + width: 4, + x: 10, + y: 15 + }); - expect(bar.inRange(10, 15)).toBe(true); - expect(bar.inRange(10, 10)).toBe(true); - expect(bar.inRange(10, 16)).toBe(false); - expect(bar.inRange(5, 5)).toBe(false); + expect(bar.inRange(10, 15)).toBe(true); + expect(bar.inRange(10, 10)).toBe(true); + expect(bar.inRange(10, 16)).toBe(false); + expect(bar.inRange(5, 5)).toBe(false); - // Test when the y is below the base (negative bar) - var negativeBar = new Chart.elements.BarElement({ - base: 0, - width: 4, - x: 10, - y: -15 - }); + // Test when the y is below the base (negative bar) + var negativeBar = new Chart.elements.BarElement({ + base: 0, + width: 4, + x: 10, + y: -15 + }); - expect(negativeBar.inRange(10, -16)).toBe(false); - expect(negativeBar.inRange(10, 1)).toBe(false); - expect(negativeBar.inRange(10, -5)).toBe(true); - }); + expect(negativeBar.inRange(10, -16)).toBe(false); + expect(negativeBar.inRange(10, 1)).toBe(false); + expect(negativeBar.inRange(10, -5)).toBe(true); + }); - it('should get the correct tooltip position', function() { - var bar = new Chart.elements.BarElement({ - base: 0, - width: 4, - x: 10, - y: 15 - }); + it('should get the correct tooltip position', function() { + var bar = new Chart.elements.BarElement({ + base: 0, + width: 4, + x: 10, + y: 15 + }); - expect(bar.tooltipPosition()).toEqual({ - x: 10, - y: 15, - }); + expect(bar.tooltipPosition()).toEqual({ + x: 10, + y: 15, + }); - // Test when the y is below the base (negative bar) - var negativeBar = new Chart.elements.BarElement({ - base: -10, - width: 4, - x: 10, - y: -15 - }); + // Test when the y is below the base (negative bar) + var negativeBar = new Chart.elements.BarElement({ + base: -10, + width: 4, + x: 10, + y: -15 + }); - expect(negativeBar.tooltipPosition()).toEqual({ - x: 10, - y: -15, - }); - }); + expect(negativeBar.tooltipPosition()).toEqual({ + x: 10, + y: -15, + }); + }); - it('should get the center', function() { - var bar = new Chart.elements.BarElement({ - base: 0, - width: 4, - x: 10, - y: 15 - }); + it('should get the center', function() { + var bar = new Chart.elements.BarElement({ + base: 0, + width: 4, + x: 10, + y: 15 + }); - expect(bar.getCenterPoint()).toEqual({x: 10, y: 7.5}); - }); + expect(bar.getCenterPoint()).toEqual({x: 10, y: 7.5}); + }); }); diff --git a/test/specs/element.line.tests.js b/test/specs/element.line.tests.js index e29d5c44f6a..b368a3e19af 100644 --- a/test/specs/element.line.tests.js +++ b/test/specs/element.line.tests.js @@ -1,35 +1,35 @@ // Tests for the line element describe('Chart.elements.LineElement', function() { - describe('auto', jasmine.fixture.specs('element.line')); + describe('auto', jasmine.fixture.specs('element.line')); - it('should be constructed', function() { - var line = new Chart.elements.LineElement({ - points: [1, 2, 3, 4] - }); + it('should be constructed', function() { + var line = new Chart.elements.LineElement({ + points: [1, 2, 3, 4] + }); - expect(line).not.toBe(undefined); - expect(line.points).toEqual([1, 2, 3, 4]); - }); + expect(line).not.toBe(undefined); + expect(line.points).toEqual([1, 2, 3, 4]); + }); - it('should not cache path when animations are enabled', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [0, -1, 0], - label: 'dataset1', - }], - labels: ['label1', 'label2', 'label3'] - }, - options: { - animation: { - duration: 50, - onComplete: () => { - expect(chart.getDatasetMeta(0).dataset._path).toBeUndefined(); - done(); - } - } - } - }); - }); + it('should not cache path when animations are enabled', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, -1, 0], + label: 'dataset1', + }], + labels: ['label1', 'label2', 'label3'] + }, + options: { + animation: { + duration: 50, + onComplete: () => { + expect(chart.getDatasetMeta(0).dataset._path).toBeUndefined(); + done(); + } + } + } + }); + }); }); diff --git a/test/specs/element.point.tests.js b/test/specs/element.point.tests.js index c73c6192c27..90526e6f72f 100644 --- a/test/specs/element.point.tests.js +++ b/test/specs/element.point.tests.js @@ -1,69 +1,69 @@ describe('Chart.elements.PointElement', function() { - describe('auto', jasmine.fixture.specs('element.point')); + describe('auto', jasmine.fixture.specs('element.point')); - it ('Should correctly identify as in range', function() { - // Mock out the point as if we were made by the controller - var point = new Chart.elements.PointElement({ - options: { - radius: 2, - hitRadius: 3, - }, - x: 10, - y: 15 - }); + it ('Should correctly identify as in range', function() { + // Mock out the point as if we were made by the controller + var point = new Chart.elements.PointElement({ + options: { + radius: 2, + hitRadius: 3, + }, + x: 10, + y: 15 + }); - expect(point.inRange(10, 15)).toBe(true); - expect(point.inRange(10, 10)).toBe(false); - expect(point.inRange(10, 5)).toBe(false); - expect(point.inRange(5, 5)).toBe(false); - }); + expect(point.inRange(10, 15)).toBe(true); + expect(point.inRange(10, 10)).toBe(false); + expect(point.inRange(10, 5)).toBe(false); + expect(point.inRange(5, 5)).toBe(false); + }); - it ('should get the correct tooltip position', function() { - // Mock out the point as if we were made by the controller - var point = new Chart.elements.PointElement({ - options: { - radius: 2, - borderWidth: 6, - }, - x: 10, - y: 15 - }); + it ('should get the correct tooltip position', function() { + // Mock out the point as if we were made by the controller + var point = new Chart.elements.PointElement({ + options: { + radius: 2, + borderWidth: 6, + }, + x: 10, + y: 15 + }); - expect(point.tooltipPosition()).toEqual({ - x: 10, - y: 15 - }); - }); + expect(point.tooltipPosition()).toEqual({ + x: 10, + y: 15 + }); + }); - it('should get the correct center point', function() { - // Mock out the point as if we were made by the controller - var point = new Chart.elements.PointElement({ - options: { - radius: 2, - }, - x: 10, - y: 10 - }); + it('should get the correct center point', function() { + // Mock out the point as if we were made by the controller + var point = new Chart.elements.PointElement({ + options: { + radius: 2, + }, + x: 10, + y: 10 + }); - expect(point.getCenterPoint()).toEqual({x: 10, y: 10}); - }); + expect(point.getCenterPoint()).toEqual({x: 10, y: 10}); + }); - it ('should not draw if skipped', function() { - var mockContext = window.createMockContext(); + it ('should not draw if skipped', function() { + var mockContext = window.createMockContext(); - // Mock out the point as if we were made by the controller - var point = new Chart.elements.PointElement({ - options: { - radius: 2, - hitRadius: 3, - }, - x: 10, - y: 15, - skip: true - }); + // Mock out the point as if we were made by the controller + var point = new Chart.elements.PointElement({ + options: { + radius: 2, + hitRadius: 3, + }, + x: 10, + y: 15, + skip: true + }); - point.draw(mockContext); + point.draw(mockContext); - expect(mockContext.getCalls()).toEqual([]); - }); + expect(mockContext.getCalls()).toEqual([]); + }); }); diff --git a/test/specs/global.defaults.tests.js b/test/specs/global.defaults.tests.js index 6974c43bf7a..a1fcf4996d1 100644 --- a/test/specs/global.defaults.tests.js +++ b/test/specs/global.defaults.tests.js @@ -1,255 +1,255 @@ describe('Default Configs', function() { - describe('Bubble Chart', function() { - it('should return correct tooltip strings', function() { - var config = Chart.defaults.controllers.bubble; - var chart = window.acquireChart({ - type: 'bubble', - data: { - datasets: [{ - label: 'My dataset', - data: [{ - x: 10, - y: 12, - r: 5 - }] - }] - }, - options: config - }); - - // fake out the tooltip hover and force the tooltip to update - chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[0], datasetIndex: 0, index: 0}]; - chart.tooltip.update(); - - // Title is always blank - expect(chart.tooltip.title).toEqual([]); - expect(chart.tooltip.body).toEqual([{ - before: [], - lines: ['My dataset: (10, 12, 5)'], - after: [] - }]); - }); - }); - - describe('Doughnut Chart', function() { - it('should return correct tooltip strings', function() { - var config = Chart.defaults.controllers.doughnut; - var chart = window.acquireChart({ - type: 'doughnut', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, 30], - }] - }, - options: config - }); - - // fake out the tooltip hover and force the tooltip to update - chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; - chart.tooltip.update(); - - // Title is always blank - expect(chart.tooltip.title).toEqual([]); - expect(chart.tooltip.body).toEqual([{ - before: [], - lines: ['label2: 20'], - after: [] - }]); - }); - - it('should return correct tooltip string for a multiline label', function() { - var config = Chart.defaults.controllers.doughnut; - var chart = window.acquireChart({ - type: 'doughnut', - data: { - labels: ['label1', ['row1', 'row2', 'row3'], 'label3'], - datasets: [{ - data: [10, 20, 30], - }] - }, - options: config - }); - - // fake out the tooltip hover and force the tooltip to update - chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; - chart.tooltip.update(); - - // Title is always blank - expect(chart.tooltip.title).toEqual([]); - expect(chart.tooltip.body).toEqual([{ - before: [], - lines: [ - 'row1: 20', - 'row2', - 'row3' - ], - after: [] - }]); - }); - - it('should return correct legend label objects', function() { - var config = Chart.defaults.controllers.doughnut; - var chart = window.acquireChart({ - type: 'doughnut', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, NaN], - backgroundColor: ['red', 'green', 'blue'], - borderWidth: 2, - borderColor: '#000' - }] - }, - options: config - }); - - var expected = [{ - text: 'label1', - fillStyle: 'red', - hidden: false, - index: 0, - strokeStyle: '#000', - lineWidth: 2 - }, { - text: 'label2', - fillStyle: 'green', - hidden: false, - index: 1, - strokeStyle: '#000', - lineWidth: 2 - }, { - text: 'label3', - fillStyle: 'blue', - hidden: false, - index: 2, - strokeStyle: '#000', - lineWidth: 2 - }]; - expect(chart.legend.legendItems).toEqual(expected); - }); - - it('should hide the correct arc when a legend item is clicked', function() { - var config = Chart.defaults.controllers.doughnut; - var chart = window.acquireChart({ - type: 'doughnut', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, NaN], - backgroundColor: ['red', 'green', 'blue'], - borderWidth: 2, - borderColor: '#000' - }] - }, - options: config - }); - spyOn(chart, 'update').and.callThrough(); - - var legendItem = chart.legend.legendItems[0]; - config.plugins.legend.onClick(null, legendItem, chart.legend); - - expect(chart.getDataVisibility(0)).toBe(false); - expect(chart.update).toHaveBeenCalled(); - - config.plugins.legend.onClick(null, legendItem, chart.legend); - expect(chart.getDataVisibility(0)).toBe(true); - }); - }); - - describe('Polar Area Chart', function() { - it('should return correct tooltip strings', function() { - var config = Chart.defaults.controllers.polarArea; - var chart = window.acquireChart({ - type: 'polarArea', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, 30], - }] - }, - options: config - }); - - // fake out the tooltip hover and force the tooltip to update - chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; - chart.tooltip.update(); - - // Title is always blank - expect(chart.tooltip.title).toEqual([]); - expect(chart.tooltip.body).toEqual([{ - before: [], - lines: ['label2: 20'], - after: [] - }]); - }); - - it('should return correct legend label objects', function() { - var config = Chart.defaults.controllers.polarArea; - var chart = window.acquireChart({ - type: 'polarArea', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, NaN], - backgroundColor: ['red', 'green', 'blue'], - borderWidth: 2, - borderColor: '#000' - }] - }, - options: config - }); - - var expected = [{ - text: 'label1', - fillStyle: 'red', - hidden: false, - index: 0, - strokeStyle: '#000', - lineWidth: 2 - }, { - text: 'label2', - fillStyle: 'green', - hidden: false, - index: 1, - strokeStyle: '#000', - lineWidth: 2 - }, { - text: 'label3', - fillStyle: 'blue', - hidden: false, - index: 2, - strokeStyle: '#000', - lineWidth: 2 - }]; - expect(chart.legend.legendItems).toEqual(expected); - }); - - it('should hide the correct arc when a legend item is clicked', function() { - var config = Chart.defaults.controllers.polarArea; - var chart = window.acquireChart({ - type: 'polarArea', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [10, 20, NaN], - backgroundColor: ['red', 'green', 'blue'], - borderWidth: 2, - borderColor: '#000' - }] - }, - options: config - }); - spyOn(chart, 'update').and.callThrough(); - - var legendItem = chart.legend.legendItems[0]; - config.plugins.legend.onClick(null, legendItem, chart.legend); - - expect(chart.getDataVisibility(0)).toBe(false); - expect(chart.update).toHaveBeenCalled(); - - config.plugins.legend.onClick(null, legendItem, chart.legend); - expect(chart.getDataVisibility(0)).toBe(true); - }); - }); + describe('Bubble Chart', function() { + it('should return correct tooltip strings', function() { + var config = Chart.defaults.controllers.bubble; + var chart = window.acquireChart({ + type: 'bubble', + data: { + datasets: [{ + label: 'My dataset', + data: [{ + x: 10, + y: 12, + r: 5 + }] + }] + }, + options: config + }); + + // fake out the tooltip hover and force the tooltip to update + chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[0], datasetIndex: 0, index: 0}]; + chart.tooltip.update(); + + // Title is always blank + expect(chart.tooltip.title).toEqual([]); + expect(chart.tooltip.body).toEqual([{ + before: [], + lines: ['My dataset: (10, 12, 5)'], + after: [] + }]); + }); + }); + + describe('Doughnut Chart', function() { + it('should return correct tooltip strings', function() { + var config = Chart.defaults.controllers.doughnut; + var chart = window.acquireChart({ + type: 'doughnut', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, 30], + }] + }, + options: config + }); + + // fake out the tooltip hover and force the tooltip to update + chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; + chart.tooltip.update(); + + // Title is always blank + expect(chart.tooltip.title).toEqual([]); + expect(chart.tooltip.body).toEqual([{ + before: [], + lines: ['label2: 20'], + after: [] + }]); + }); + + it('should return correct tooltip string for a multiline label', function() { + var config = Chart.defaults.controllers.doughnut; + var chart = window.acquireChart({ + type: 'doughnut', + data: { + labels: ['label1', ['row1', 'row2', 'row3'], 'label3'], + datasets: [{ + data: [10, 20, 30], + }] + }, + options: config + }); + + // fake out the tooltip hover and force the tooltip to update + chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; + chart.tooltip.update(); + + // Title is always blank + expect(chart.tooltip.title).toEqual([]); + expect(chart.tooltip.body).toEqual([{ + before: [], + lines: [ + 'row1: 20', + 'row2', + 'row3' + ], + after: [] + }]); + }); + + it('should return correct legend label objects', function() { + var config = Chart.defaults.controllers.doughnut; + var chart = window.acquireChart({ + type: 'doughnut', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, NaN], + backgroundColor: ['red', 'green', 'blue'], + borderWidth: 2, + borderColor: '#000' + }] + }, + options: config + }); + + var expected = [{ + text: 'label1', + fillStyle: 'red', + hidden: false, + index: 0, + strokeStyle: '#000', + lineWidth: 2 + }, { + text: 'label2', + fillStyle: 'green', + hidden: false, + index: 1, + strokeStyle: '#000', + lineWidth: 2 + }, { + text: 'label3', + fillStyle: 'blue', + hidden: false, + index: 2, + strokeStyle: '#000', + lineWidth: 2 + }]; + expect(chart.legend.legendItems).toEqual(expected); + }); + + it('should hide the correct arc when a legend item is clicked', function() { + var config = Chart.defaults.controllers.doughnut; + var chart = window.acquireChart({ + type: 'doughnut', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, NaN], + backgroundColor: ['red', 'green', 'blue'], + borderWidth: 2, + borderColor: '#000' + }] + }, + options: config + }); + spyOn(chart, 'update').and.callThrough(); + + var legendItem = chart.legend.legendItems[0]; + config.plugins.legend.onClick(null, legendItem, chart.legend); + + expect(chart.getDataVisibility(0)).toBe(false); + expect(chart.update).toHaveBeenCalled(); + + config.plugins.legend.onClick(null, legendItem, chart.legend); + expect(chart.getDataVisibility(0)).toBe(true); + }); + }); + + describe('Polar Area Chart', function() { + it('should return correct tooltip strings', function() { + var config = Chart.defaults.controllers.polarArea; + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, 30], + }] + }, + options: config + }); + + // fake out the tooltip hover and force the tooltip to update + chart.tooltip._active = [{element: chart.getDatasetMeta(0).data[1], datasetIndex: 0, index: 1}]; + chart.tooltip.update(); + + // Title is always blank + expect(chart.tooltip.title).toEqual([]); + expect(chart.tooltip.body).toEqual([{ + before: [], + lines: ['label2: 20'], + after: [] + }]); + }); + + it('should return correct legend label objects', function() { + var config = Chart.defaults.controllers.polarArea; + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, NaN], + backgroundColor: ['red', 'green', 'blue'], + borderWidth: 2, + borderColor: '#000' + }] + }, + options: config + }); + + var expected = [{ + text: 'label1', + fillStyle: 'red', + hidden: false, + index: 0, + strokeStyle: '#000', + lineWidth: 2 + }, { + text: 'label2', + fillStyle: 'green', + hidden: false, + index: 1, + strokeStyle: '#000', + lineWidth: 2 + }, { + text: 'label3', + fillStyle: 'blue', + hidden: false, + index: 2, + strokeStyle: '#000', + lineWidth: 2 + }]; + expect(chart.legend.legendItems).toEqual(expected); + }); + + it('should hide the correct arc when a legend item is clicked', function() { + var config = Chart.defaults.controllers.polarArea; + var chart = window.acquireChart({ + type: 'polarArea', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [10, 20, NaN], + backgroundColor: ['red', 'green', 'blue'], + borderWidth: 2, + borderColor: '#000' + }] + }, + options: config + }); + spyOn(chart, 'update').and.callThrough(); + + var legendItem = chart.legend.legendItems[0]; + config.plugins.legend.onClick(null, legendItem, chart.legend); + + expect(chart.getDataVisibility(0)).toBe(false); + expect(chart.update).toHaveBeenCalled(); + + config.plugins.legend.onClick(null, legendItem, chart.legend); + expect(chart.getDataVisibility(0)).toBe(true); + }); + }); }); diff --git a/test/specs/global.namespace.tests.js b/test/specs/global.namespace.tests.js index 1dca63dc035..011bfc21409 100644 --- a/test/specs/global.namespace.tests.js +++ b/test/specs/global.namespace.tests.js @@ -1,39 +1,39 @@ describe('Chart namespace', function() { - describe('Chart', function() { - it('should a function (constructor)', function() { - expect(Chart instanceof Function).toBeTruthy(); - }); - it('should define "core" properties', function() { - expect(Chart instanceof Function).toBeTruthy(); - expect(Chart.Animation instanceof Object).toBeTruthy(); - expect(Chart.Animations instanceof Object).toBeTruthy(); - expect(Chart.defaults instanceof Object).toBeTruthy(); - expect(Chart.Element instanceof Object).toBeTruthy(); - expect(Chart.Interaction instanceof Object).toBeTruthy(); - expect(Chart.layouts instanceof Object).toBeTruthy(); + describe('Chart', function() { + it('should a function (constructor)', function() { + expect(Chart instanceof Function).toBeTruthy(); + }); + it('should define "core" properties', function() { + expect(Chart instanceof Function).toBeTruthy(); + expect(Chart.Animation instanceof Object).toBeTruthy(); + expect(Chart.Animations instanceof Object).toBeTruthy(); + expect(Chart.defaults instanceof Object).toBeTruthy(); + expect(Chart.Element instanceof Object).toBeTruthy(); + expect(Chart.Interaction instanceof Object).toBeTruthy(); + expect(Chart.layouts instanceof Object).toBeTruthy(); - expect(Chart.platforms.BasePlatform instanceof Function).toBeTruthy(); - expect(Chart.platforms.BasicPlatform instanceof Function).toBeTruthy(); - expect(Chart.platforms.DomPlatform instanceof Function).toBeTruthy(); + expect(Chart.platforms.BasePlatform instanceof Function).toBeTruthy(); + expect(Chart.platforms.BasicPlatform instanceof Function).toBeTruthy(); + expect(Chart.platforms.DomPlatform instanceof Function).toBeTruthy(); - expect(Chart.registry instanceof Object).toBeTruthy(); - expect(Chart.Scale instanceof Object).toBeTruthy(); - expect(Chart.Ticks instanceof Object).toBeTruthy(); - }); - }); + expect(Chart.registry instanceof Object).toBeTruthy(); + expect(Chart.Scale instanceof Object).toBeTruthy(); + expect(Chart.Ticks instanceof Object).toBeTruthy(); + }); + }); - describe('Chart.elements', function() { - it('should contains "elements" classes', function() { - expect(Chart.elements.ArcElement instanceof Function).toBeTruthy(); - expect(Chart.elements.BarElement instanceof Function).toBeTruthy(); - expect(Chart.elements.LineElement instanceof Function).toBeTruthy(); - expect(Chart.elements.PointElement instanceof Function).toBeTruthy(); - }); - }); + describe('Chart.elements', function() { + it('should contains "elements" classes', function() { + expect(Chart.elements.ArcElement instanceof Function).toBeTruthy(); + expect(Chart.elements.BarElement instanceof Function).toBeTruthy(); + expect(Chart.elements.LineElement instanceof Function).toBeTruthy(); + expect(Chart.elements.PointElement instanceof Function).toBeTruthy(); + }); + }); - describe('Chart.helpers', function() { - it('should be an object', function() { - expect(Chart.helpers instanceof Object).toBeTruthy(); - }); - }); + describe('Chart.helpers', function() { + it('should be an object', function() { + expect(Chart.helpers instanceof Object).toBeTruthy(); + }); + }); }); diff --git a/test/specs/helpers.canvas.tests.js b/test/specs/helpers.canvas.tests.js index 0008908926d..e451d7f1331 100644 --- a/test/specs/helpers.canvas.tests.js +++ b/test/specs/helpers.canvas.tests.js @@ -1,337 +1,337 @@ 'use strict'; describe('Chart.helpers.canvas', function() { - describe('auto', jasmine.fixture.specs('helpers')); + describe('auto', jasmine.fixture.specs('helpers')); - var helpers = Chart.helpers; + var helpers = Chart.helpers; - describe('clearCanvas', function() { - it('should clear the chart canvas', function() { - var chart = acquireChart({}, { - canvas: { - style: 'width: 150px; height: 245px' - } - }); + describe('clearCanvas', function() { + it('should clear the chart canvas', function() { + var chart = acquireChart({}, { + canvas: { + style: 'width: 150px; height: 245px' + } + }); - spyOn(chart.ctx, 'clearRect'); + spyOn(chart.ctx, 'clearRect'); - helpers.clearCanvas(chart.canvas, chart.ctx); + helpers.clearCanvas(chart.canvas, chart.ctx); - expect(chart.ctx.clearRect.calls.count()).toBe(1); - expect(chart.ctx.clearRect.calls.first().object).toBe(chart.ctx); - expect(chart.ctx.clearRect.calls.first().args).toEqual([0, 0, 150, 245]); - }); - }); + expect(chart.ctx.clearRect.calls.count()).toBe(1); + expect(chart.ctx.clearRect.calls.first().object).toBe(chart.ctx); + expect(chart.ctx.clearRect.calls.first().args).toEqual([0, 0, 150, 245]); + }); + }); - describe('isPointInArea', function() { - it('should determine if a point is in the area', function() { - var isPointInArea = helpers._isPointInArea; - var area = {left: 0, top: 0, right: 512, bottom: 256}; + describe('isPointInArea', function() { + it('should determine if a point is in the area', function() { + var isPointInArea = helpers._isPointInArea; + var area = {left: 0, top: 0, right: 512, bottom: 256}; - expect(isPointInArea({x: 0, y: 0}, area)).toBe(true); - expect(isPointInArea({x: -1e-12, y: -1e-12}, area)).toBe(true); - expect(isPointInArea({x: 512, y: 256}, area)).toBe(true); - expect(isPointInArea({x: 512 + 1e-12, y: 256 + 1e-12}, area)).toBe(true); - expect(isPointInArea({x: -0.5, y: 0}, area)).toBe(false); - expect(isPointInArea({x: 0, y: 256.5}, area)).toBe(false); - }); - }); + expect(isPointInArea({x: 0, y: 0}, area)).toBe(true); + expect(isPointInArea({x: -1e-12, y: -1e-12}, area)).toBe(true); + expect(isPointInArea({x: 512, y: 256}, area)).toBe(true); + expect(isPointInArea({x: 512 + 1e-12, y: 256 + 1e-12}, area)).toBe(true); + expect(isPointInArea({x: -0.5, y: 0}, area)).toBe(false); + expect(isPointInArea({x: 0, y: 256.5}, area)).toBe(false); + }); + }); - it('should return the width of the longest text in an Array and 2D Array', function() { - var context = window.createMockContext(); - var font = "normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"; - var arrayOfThings1D = ['FooBar', 'Bar']; - var arrayOfThings2D = [['FooBar_1', 'Bar_2'], 'Foo_1']; + it('should return the width of the longest text in an Array and 2D Array', function() { + var context = window.createMockContext(); + var font = "normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"; + var arrayOfThings1D = ['FooBar', 'Bar']; + var arrayOfThings2D = [['FooBar_1', 'Bar_2'], 'Foo_1']; - // Regardless 'FooBar' is the longest label it should return (characters * 10) - expect(helpers._longestText(context, font, arrayOfThings1D, {})).toEqual(60); - expect(helpers._longestText(context, font, arrayOfThings2D, {})).toEqual(80); - // We check to make sure we made the right calls to the canvas. - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [] - }, { - name: 'setFont', - args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], - }, { - name: 'measureText', - args: ['FooBar'] - }, { - name: 'measureText', - args: ['Bar'] - }, { - name: 'restore', - args: [] - }, { - name: 'save', - args: [] - }, { - name: 'setFont', - args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], - }, { - name: 'measureText', - args: ['FooBar_1'] - }, { - name: 'measureText', - args: ['Bar_2'] - }, { - name: 'measureText', - args: ['Foo_1'] - }, { - name: 'restore', - args: [] - }]); - }); + // Regardless 'FooBar' is the longest label it should return (characters * 10) + expect(helpers._longestText(context, font, arrayOfThings1D, {})).toEqual(60); + expect(helpers._longestText(context, font, arrayOfThings2D, {})).toEqual(80); + // We check to make sure we made the right calls to the canvas. + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [] + }, { + name: 'setFont', + args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], + }, { + name: 'measureText', + args: ['FooBar'] + }, { + name: 'measureText', + args: ['Bar'] + }, { + name: 'restore', + args: [] + }, { + name: 'save', + args: [] + }, { + name: 'setFont', + args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], + }, { + name: 'measureText', + args: ['FooBar_1'] + }, { + name: 'measureText', + args: ['Bar_2'] + }, { + name: 'measureText', + args: ['Foo_1'] + }, { + name: 'restore', + args: [] + }]); + }); - it('compare text with current longest and update', function() { - var context = window.createMockContext(); - var data = {}; - var gc = []; - var longest = 70; + it('compare text with current longest and update', function() { + var context = window.createMockContext(); + var data = {}; + var gc = []; + var longest = 70; - expect(helpers._measureText(context, data, gc, longest, 'foobar')).toEqual(70); - expect(helpers._measureText(context, data, gc, longest, 'foobar_')).toEqual(70); - expect(helpers._measureText(context, data, gc, longest, 'foobar_1')).toEqual(80); - // We check to make sure we made the right calls to the canvas. - expect(context.getCalls()).toEqual([{ - name: 'measureText', - args: ['foobar'] - }, { - name: 'measureText', - args: ['foobar_'] - }, { - name: 'measureText', - args: ['foobar_1'] - }]); - }); + expect(helpers._measureText(context, data, gc, longest, 'foobar')).toEqual(70); + expect(helpers._measureText(context, data, gc, longest, 'foobar_')).toEqual(70); + expect(helpers._measureText(context, data, gc, longest, 'foobar_1')).toEqual(80); + // We check to make sure we made the right calls to the canvas. + expect(context.getCalls()).toEqual([{ + name: 'measureText', + args: ['foobar'] + }, { + name: 'measureText', + args: ['foobar_'] + }, { + name: 'measureText', + args: ['foobar_1'] + }]); + }); - describe('renderText', function() { - it('should render multiple lines of text', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, ['foo', 'foo2'], 0, 0, font); + describe('renderText', function() { + it('should render multiple lines of text', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, ['foo', 'foo2'], 0, 0, font); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'fillText', - args: ['foo2', 0, 20, undefined], - }, { - name: 'restore', - args: [], - }]); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'fillText', + args: ['foo2', 0, 20, undefined], + }, { + name: 'restore', + args: [], + }]); + }); - it('should accept the text maxWidth', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {maxWidth: 30}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'fillText', - args: ['foo', 0, 0, 30], - }, { - name: 'restore', - args: [], - }]); - }); + it('should accept the text maxWidth', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {maxWidth: 30}); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'fillText', + args: ['foo', 0, 0, 30], + }, { + name: 'restore', + args: [], + }]); + }); - it('should underline the text', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {decorationWidth: 3, underline: true}); + it('should underline the text', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {decorationWidth: 3, underline: true}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'measureText', - args: ['foo'], - }, { - name: 'setStrokeStyle', - args: [null], - }, { - name: 'beginPath', - args: [], - }, { - name: 'setLineWidth', - args: [3], - }, { - name: 'moveTo', - args: [-15, 8], - }, { - name: 'lineTo', - args: [25, 8], - }, { - name: 'stroke', - args: [], - }, { - name: 'restore', - args: [], - }]); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'measureText', + args: ['foo'], + }, { + name: 'setStrokeStyle', + args: [null], + }, { + name: 'beginPath', + args: [], + }, { + name: 'setLineWidth', + args: [3], + }, { + name: 'moveTo', + args: [-15, 8], + }, { + name: 'lineTo', + args: [25, 8], + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [], + }]); + }); - it('should strikethrough the text', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {strikethrough: true}); + it('should strikethrough the text', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {strikethrough: true}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'measureText', - args: ['foo'], - }, { - name: 'setStrokeStyle', - args: [null], - }, { - name: 'beginPath', - args: [], - }, { - name: 'setLineWidth', - args: [2], - }, { - name: 'moveTo', - args: [-15, 2], - }, { - name: 'lineTo', - args: [25, 2], - }, { - name: 'stroke', - args: [], - }, { - name: 'restore', - args: [], - }]); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'measureText', + args: ['foo'], + }, { + name: 'setStrokeStyle', + args: [null], + }, { + name: 'beginPath', + args: [], + }, { + name: 'setLineWidth', + args: [2], + }, { + name: 'moveTo', + args: [-15, 2], + }, { + name: 'lineTo', + args: [25, 2], + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [], + }]); + }); - it('should set the fill style if supplied', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {color: 'red'}); + it('should set the fill style if supplied', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {color: 'red'}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'setFillStyle', - args: ['red'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'restore', - args: [], - }]); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'setFillStyle', + args: ['red'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'restore', + args: [], + }]); + }); - it('should set the stroke style if supplied', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {strokeColor: 'red', strokeWidth: 2}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'setStrokeStyle', - args: ['red'], - }, { - name: 'setLineWidth', - args: [2], - }, { - name: 'strokeText', - args: ['foo', 0, 0, undefined], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'restore', - args: [], - }]); - }); + it('should set the stroke style if supplied', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {strokeColor: 'red', strokeWidth: 2}); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'setStrokeStyle', + args: ['red'], + }, { + name: 'setLineWidth', + args: [2], + }, { + name: 'strokeText', + args: ['foo', 0, 0, undefined], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'restore', + args: [], + }]); + }); - it('should set the text alignment', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {textAlign: 'left', textBaseline: 'middle'}); + it('should set the text alignment', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {textAlign: 'left', textBaseline: 'middle'}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'setTextAlign', - args: ['left'], - }, { - name: 'setTextBaseline', - args: ['middle'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'restore', - args: [], - }]); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'setTextAlign', + args: ['left'], + }, { + name: 'setTextBaseline', + args: ['middle'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'restore', + args: [], + }]); + }); - it('should translate and rotate text', function() { - var context = window.createMockContext(); - var font = {string: '12px arial', lineHeight: 20}; - helpers.renderText(context, 'foo', 0, 0, font, {rotation: 90, translation: [10, 20]}); + it('should translate and rotate text', function() { + var context = window.createMockContext(); + var font = {string: '12px arial', lineHeight: 20}; + helpers.renderText(context, 'foo', 0, 0, font, {rotation: 90, translation: [10, 20]}); - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [], - }, { - name: 'translate', - args: [10, 20], - }, { - name: 'rotate', - args: [90], - }, { - name: 'setFont', - args: ['12px arial'], - }, { - name: 'fillText', - args: ['foo', 0, 0, undefined], - }, { - name: 'restore', - args: [], - }]); - }); - }); + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'translate', + args: [10, 20], + }, { + name: 'rotate', + args: [90], + }, { + name: 'setFont', + args: ['12px arial'], + }, { + name: 'fillText', + args: ['foo', 0, 0, undefined], + }, { + name: 'restore', + args: [], + }]); + }); + }); }); diff --git a/test/specs/helpers.collection.tests.js b/test/specs/helpers.collection.tests.js index cae35c200d7..f6a1cbae5eb 100644 --- a/test/specs/helpers.collection.tests.js +++ b/test/specs/helpers.collection.tests.js @@ -1,36 +1,36 @@ const {_filterBetween, _lookup, _lookupByKey, _rlookupByKey} = Chart.helpers; describe('helpers.collection', function() { - it('Should do binary search', function() { - const data = [0, 2, 6, 9]; - expect(_lookup(data, 0)).toEqual({lo: 0, hi: 1}); - expect(_lookup(data, 1)).toEqual({lo: 0, hi: 1}); - expect(_lookup(data, 3)).toEqual({lo: 1, hi: 2}); - expect(_lookup(data, 6)).toEqual({lo: 1, hi: 2}); - expect(_lookup(data, 9)).toEqual({lo: 2, hi: 3}); - }); + it('Should do binary search', function() { + const data = [0, 2, 6, 9]; + expect(_lookup(data, 0)).toEqual({lo: 0, hi: 1}); + expect(_lookup(data, 1)).toEqual({lo: 0, hi: 1}); + expect(_lookup(data, 3)).toEqual({lo: 1, hi: 2}); + expect(_lookup(data, 6)).toEqual({lo: 1, hi: 2}); + expect(_lookup(data, 9)).toEqual({lo: 2, hi: 3}); + }); - it('Should do binary search by key', function() { - const data = [{x: 0}, {x: 2}, {x: 6}, {x: 9}]; - expect(_lookupByKey(data, 'x', 0)).toEqual({lo: 0, hi: 1}); - expect(_lookupByKey(data, 'x', 1)).toEqual({lo: 0, hi: 1}); - expect(_lookupByKey(data, 'x', 3)).toEqual({lo: 1, hi: 2}); - expect(_lookupByKey(data, 'x', 6)).toEqual({lo: 1, hi: 2}); - expect(_lookupByKey(data, 'x', 9)).toEqual({lo: 2, hi: 3}); - }); + it('Should do binary search by key', function() { + const data = [{x: 0}, {x: 2}, {x: 6}, {x: 9}]; + expect(_lookupByKey(data, 'x', 0)).toEqual({lo: 0, hi: 1}); + expect(_lookupByKey(data, 'x', 1)).toEqual({lo: 0, hi: 1}); + expect(_lookupByKey(data, 'x', 3)).toEqual({lo: 1, hi: 2}); + expect(_lookupByKey(data, 'x', 6)).toEqual({lo: 1, hi: 2}); + expect(_lookupByKey(data, 'x', 9)).toEqual({lo: 2, hi: 3}); + }); - it('Should do reverse binary search by key', function() { - const data = [{x: 10}, {x: 7}, {x: 3}, {x: 0}]; - expect(_rlookupByKey(data, 'x', 0)).toEqual({lo: 2, hi: 3}); - expect(_rlookupByKey(data, 'x', 3)).toEqual({lo: 2, hi: 3}); - expect(_rlookupByKey(data, 'x', 5)).toEqual({lo: 1, hi: 2}); - expect(_rlookupByKey(data, 'x', 8)).toEqual({lo: 0, hi: 1}); - expect(_rlookupByKey(data, 'x', 10)).toEqual({lo: 0, hi: 1}); - }); + it('Should do reverse binary search by key', function() { + const data = [{x: 10}, {x: 7}, {x: 3}, {x: 0}]; + expect(_rlookupByKey(data, 'x', 0)).toEqual({lo: 2, hi: 3}); + expect(_rlookupByKey(data, 'x', 3)).toEqual({lo: 2, hi: 3}); + expect(_rlookupByKey(data, 'x', 5)).toEqual({lo: 1, hi: 2}); + expect(_rlookupByKey(data, 'x', 8)).toEqual({lo: 0, hi: 1}); + expect(_rlookupByKey(data, 'x', 10)).toEqual({lo: 0, hi: 1}); + }); - it('Should filter a sorted array', function() { - expect(_filterBetween([1, 2, 3, 4, 5, 6, 7, 8, 9], 5, 8)).toEqual([5, 6, 7, 8]); - expect(_filterBetween([1], 1, 1)).toEqual([1]); - expect(_filterBetween([1583049600000], 1584816327553, 1585680327553)).toEqual([]); - }); + it('Should filter a sorted array', function() { + expect(_filterBetween([1, 2, 3, 4, 5, 6, 7, 8, 9], 5, 8)).toEqual([5, 6, 7, 8]); + expect(_filterBetween([1], 1, 1)).toEqual([1]); + expect(_filterBetween([1583049600000], 1584816327553, 1585680327553)).toEqual([]); + }); }); diff --git a/test/specs/helpers.color.tests.js b/test/specs/helpers.color.tests.js index 6f478ddf46d..228d1980774 100644 --- a/test/specs/helpers.color.tests.js +++ b/test/specs/helpers.color.tests.js @@ -1,47 +1,47 @@ const {color, getHoverColor} = Chart.helpers; describe('Color helper', function() { - function isColorInstance(obj) { - return typeof obj === 'object' && obj.valid; - } + function isColorInstance(obj) { + return typeof obj === 'object' && obj.valid; + } - it('should return a color when called with a color', function() { - expect(isColorInstance(color('rgb(1, 2, 3)'))).toBe(true); - }); + it('should return a color when called with a color', function() { + expect(isColorInstance(color('rgb(1, 2, 3)'))).toBe(true); + }); }); describe('Background hover color helper', function() { - it('should return a modified version of color when called with a color', function() { - var originalColorRGB = 'rgb(70, 191, 189)'; + it('should return a modified version of color when called with a color', function() { + var originalColorRGB = 'rgb(70, 191, 189)'; - expect(getHoverColor('#46BFBD')).not.toEqual(originalColorRGB); - }); + expect(getHoverColor('#46BFBD')).not.toEqual(originalColorRGB); + }); }); describe('color and getHoverColor helpers', function() { - it('should return a CanvasPattern when called with a CanvasPattern', function(done) { - var dots = new Image(); - dots.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAD1BMVEUAAAD///////////////+PQt5oAAAABXRSTlMAHlFhZsfk/BEAAAAqSURBVHgBY2BgZGJmYmSAAUYWEIDzmcBcJhiXGcxlRpPFrhdmMiqgvX0AcGIBEUAo6UAAAAAASUVORK5CYII='; - dots.onload = function() { - var chartContext = document.createElement('canvas').getContext('2d'); - var patternCanvas = document.createElement('canvas'); - var patternContext = patternCanvas.getContext('2d'); - var pattern = patternContext.createPattern(dots, 'repeat'); - patternContext.fillStyle = pattern; - var chartPattern = chartContext.createPattern(patternCanvas, 'repeat'); - - expect(color(chartPattern) instanceof CanvasPattern).toBe(true); - expect(getHoverColor(chartPattern) instanceof CanvasPattern).toBe(true); - - done(); - }; - }); - - 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(color(gradient) instanceof CanvasGradient).toBe(true); - expect(getHoverColor(gradient) instanceof CanvasGradient).toBe(true); - }); + it('should return a CanvasPattern when called with a CanvasPattern', function(done) { + var dots = new Image(); + dots.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAD1BMVEUAAAD///////////////+PQt5oAAAABXRSTlMAHlFhZsfk/BEAAAAqSURBVHgBY2BgZGJmYmSAAUYWEIDzmcBcJhiXGcxlRpPFrhdmMiqgvX0AcGIBEUAo6UAAAAAASUVORK5CYII='; + dots.onload = function() { + var chartContext = document.createElement('canvas').getContext('2d'); + var patternCanvas = document.createElement('canvas'); + var patternContext = patternCanvas.getContext('2d'); + var pattern = patternContext.createPattern(dots, 'repeat'); + patternContext.fillStyle = pattern; + var chartPattern = chartContext.createPattern(patternCanvas, 'repeat'); + + expect(color(chartPattern) instanceof CanvasPattern).toBe(true); + expect(getHoverColor(chartPattern) instanceof CanvasPattern).toBe(true); + + done(); + }; + }); + + 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(color(gradient) instanceof CanvasGradient).toBe(true); + expect(getHoverColor(gradient) instanceof CanvasGradient).toBe(true); + }); }); diff --git a/test/specs/helpers.core.tests.js b/test/specs/helpers.core.tests.js index b491686f804..fb4e34f53bf 100644 --- a/test/specs/helpers.core.tests.js +++ b/test/specs/helpers.core.tests.js @@ -1,460 +1,460 @@ 'use strict'; describe('Chart.helpers.core', function() { - var helpers = Chart.helpers; - - describe('noop', function() { - it('should be callable', function() { - expect(helpers.noop).toBeDefined(); - expect(typeof helpers.noop).toBe('function'); - expect(typeof helpers.noop.call).toBe('function'); - }); - it('should returns "undefined"', function() { - expect(helpers.noop(42)).not.toBeDefined(); - expect(helpers.noop.call(this, 42)).not.toBeDefined(); - }); - }); - - describe('isArray', function() { - it('should return true if value is an array', function() { - expect(helpers.isArray([])).toBeTruthy(); - expect(helpers.isArray([42])).toBeTruthy(); - expect(helpers.isArray(new Array())).toBeTruthy(); - expect(helpers.isArray(Array.prototype)).toBeTruthy(); - expect(helpers.isArray(new Int8Array(2))).toBeTruthy(); - expect(helpers.isArray(new Uint8Array())).toBeTruthy(); - expect(helpers.isArray(new Uint8ClampedArray([128, 244]))).toBeTruthy(); - expect(helpers.isArray(new Int16Array())).toBeTruthy(); - expect(helpers.isArray(new Uint16Array())).toBeTruthy(); - expect(helpers.isArray(new Int32Array())).toBeTruthy(); - expect(helpers.isArray(new Uint32Array())).toBeTruthy(); - expect(helpers.isArray(new Float32Array([1.2]))).toBeTruthy(); - expect(helpers.isArray(new Float64Array([]))).toBeTruthy(); - }); - it('should return false if value is not an array', function() { - expect(helpers.isArray()).toBeFalsy(); - expect(helpers.isArray({})).toBeFalsy(); - expect(helpers.isArray(undefined)).toBeFalsy(); - expect(helpers.isArray(null)).toBeFalsy(); - expect(helpers.isArray(true)).toBeFalsy(); - expect(helpers.isArray(false)).toBeFalsy(); - expect(helpers.isArray(42)).toBeFalsy(); - expect(helpers.isArray('Array')).toBeFalsy(); - expect(helpers.isArray({__proto__: Array.prototype})).toBeFalsy(); - }); - }); - - describe('isObject', function() { - it('should return true if value is an object', function() { - expect(helpers.isObject({})).toBeTruthy(); - expect(helpers.isObject({a: 42})).toBeTruthy(); - expect(helpers.isObject(new Object())).toBeTruthy(); - }); - it('should return false if value is not an object', function() { - expect(helpers.isObject()).toBeFalsy(); - expect(helpers.isObject(undefined)).toBeFalsy(); - expect(helpers.isObject(null)).toBeFalsy(); - expect(helpers.isObject(true)).toBeFalsy(); - expect(helpers.isObject(false)).toBeFalsy(); - expect(helpers.isObject(42)).toBeFalsy(); - expect(helpers.isObject('Object')).toBeFalsy(); - expect(helpers.isObject([])).toBeFalsy(); - expect(helpers.isObject([42])).toBeFalsy(); - expect(helpers.isObject(new Array())).toBeFalsy(); - expect(helpers.isObject(new Date())).toBeFalsy(); - }); - }); - - 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(); - expect(helpers.isNullOrUndef(undefined)).toBeTruthy(); - }); - it('should return false if value is not null/undefined', function() { - expect(helpers.isNullOrUndef(true)).toBeFalsy(); - expect(helpers.isNullOrUndef(false)).toBeFalsy(); - expect(helpers.isNullOrUndef('')).toBeFalsy(); - expect(helpers.isNullOrUndef('String')).toBeFalsy(); - expect(helpers.isNullOrUndef(0)).toBeFalsy(); - expect(helpers.isNullOrUndef([])).toBeFalsy(); - expect(helpers.isNullOrUndef({})).toBeFalsy(); - expect(helpers.isNullOrUndef([42])).toBeFalsy(); - expect(helpers.isNullOrUndef(new Date())).toBeFalsy(); - }); - }); - - describe('valueOrDefault', function() { - it('should return value if defined', function() { - var object = {}; - var array = []; - - expect(helpers.valueOrDefault(null, 42)).toBe(null); - expect(helpers.valueOrDefault(false, 42)).toBe(false); - expect(helpers.valueOrDefault(object, 42)).toBe(object); - expect(helpers.valueOrDefault(array, 42)).toBe(array); - expect(helpers.valueOrDefault('', 42)).toBe(''); - expect(helpers.valueOrDefault(0, 42)).toBe(0); - }); - it('should return default if undefined', function() { - expect(helpers.valueOrDefault(undefined, 42)).toBe(42); - expect(helpers.valueOrDefault({}.foo, 42)).toBe(42); - }); - }); - - describe('callback', function() { - it('should return undefined if fn is not a function', function() { - expect(helpers.callback()).not.toBeDefined(); - expect(helpers.callback(null)).not.toBeDefined(); - expect(helpers.callback(42)).not.toBeDefined(); - expect(helpers.callback([])).not.toBeDefined(); - expect(helpers.callback({})).not.toBeDefined(); - }); - it('should call fn with the given args', function() { - var spy = jasmine.createSpy('spy'); - helpers.callback(spy); - helpers.callback(spy, []); - helpers.callback(spy, ['foo']); - helpers.callback(spy, [42, 'bar']); - - expect(spy.calls.argsFor(0)).toEqual([]); - expect(spy.calls.argsFor(1)).toEqual([]); - expect(spy.calls.argsFor(2)).toEqual(['foo']); - expect(spy.calls.argsFor(3)).toEqual([42, 'bar']); - }); - it('should call fn with the given scope', function() { - var spy = jasmine.createSpy('spy'); - var scope = {}; - - helpers.callback(spy); - helpers.callback(spy, [], null); - helpers.callback(spy, [], undefined); - helpers.callback(spy, [], scope); - - expect(spy.calls.all()[0].object).toBe(window); - expect(spy.calls.all()[1].object).toBe(window); - expect(spy.calls.all()[2].object).toBe(window); - expect(spy.calls.all()[3].object).toBe(scope); - }); - it('should return the value returned by fn', function() { - expect(helpers.callback(helpers.noop, [41])).toBe(undefined); - expect(helpers.callback(function(i) { - return i + 1; - }, [41])).toBe(42); - }); - }); - - describe('each', function() { - it('should iterate over an array forward if reverse === false', function() { - var scope = {}; - var scopes = []; - var items = []; - var keys = []; - - helpers.each(['foo', 'bar', 42], function(item, key) { - scopes.push(this); - items.push(item); - keys.push(key); - }, scope); - - expect(scopes).toEqual([scope, scope, scope]); - expect(items).toEqual(['foo', 'bar', 42]); - expect(keys).toEqual([0, 1, 2]); - }); - it('should iterate over an array backward if reverse === true', function() { - var scope = {}; - var scopes = []; - var items = []; - var keys = []; - - helpers.each(['foo', 'bar', 42], function(item, key) { - scopes.push(this); - items.push(item); - keys.push(key); - }, scope, true); - - expect(scopes).toEqual([scope, scope, scope]); - expect(items).toEqual([42, 'bar', 'foo']); - expect(keys).toEqual([2, 1, 0]); - }); - it('should iterate over object properties', function() { - var scope = {}; - var scopes = []; - var items = []; - - helpers.each({a: 'foo', b: 'bar', c: 42}, function(item, key) { - scopes.push(this); - items[key] = item; - }, scope); - - expect(scopes).toEqual([scope, scope, scope]); - expect(items).toEqual(jasmine.objectContaining({a: 'foo', b: 'bar', c: 42})); - }); - it('should not throw when called with a non iterable object', function() { - expect(function() { - helpers.each(undefined); - }).not.toThrow(); - expect(function() { - helpers.each(null); - }).not.toThrow(); - expect(function() { - helpers.each(42); - }).not.toThrow(); - }); - }); - - describe('_elementsEqual', function() { - it('should return true if arrays are the same', function() { - expect(helpers._elementsEqual( - [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}], - [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}])).toBeTruthy(); - }); - it('should return false if arrays are not the same', function() { - expect(helpers._elementsEqual([], [{datasetIndex: 0, index: 1}])).toBeFalsy(); - expect(helpers._elementsEqual([{datasetIndex: 0, index: 2}], [{datasetIndex: 0, index: 1}])).toBeFalsy(); - }); - }); - - describe('clone', function() { - it('should clone primitive values', function() { - expect(helpers.clone()).toBe(undefined); - expect(helpers.clone(null)).toBe(null); - expect(helpers.clone(true)).toBe(true); - expect(helpers.clone(42)).toBe(42); - expect(helpers.clone('foo')).toBe('foo'); - }); - it('should perform a deep copy of arrays', function() { - var o0 = {a: 42}; - var o1 = {s: 's'}; - var a0 = ['bar']; - var a1 = [a0, o0, 2]; - var f0 = function() {}; - var input = [a1, o1, f0, 42, 'foo']; - var output = helpers.clone(input); - - expect(output).toEqual(input); - expect(output).not.toBe(input); - expect(output[0]).not.toBe(a1); - expect(output[0][0]).not.toBe(a0); - expect(output[1]).not.toBe(o1); - }); - it('should perform a deep copy of objects', function() { - var a0 = ['bar']; - var a1 = [1, 2, 3]; - var o0 = {a: a1, i: 42}; - var f0 = function() {}; - var input = {o: o0, a: a0, f: f0, s: 'foo', i: 42}; - var output = helpers.clone(input); - - expect(output).toEqual(input); - expect(output).not.toBe(input); - expect(output.o).not.toBe(o0); - expect(output.o.a).not.toBe(a1); - expect(output.a).not.toBe(a0); - }); - }); - - describe('merge', function() { - it('should not allow prototype pollution', function() { - var test = helpers.merge({}, JSON.parse('{"__proto__":{"polluted": true}}')); - expect(test.prototype).toBeUndefined(); - expect(Object.prototype.polluted).toBeUndefined(); - }); - it('should update target and return it', function() { - var target = {a: 1}; - var result = helpers.merge(target, {a: 2, b: 'foo'}); - expect(target).toEqual({a: 2, b: 'foo'}); - expect(target).toBe(result); - }); - it('should return target if not an object', function() { - expect(helpers.merge(undefined, {a: 42})).toEqual(undefined); - expect(helpers.merge(null, {a: 42})).toEqual(null); - expect(helpers.merge('foo', {a: 42})).toEqual('foo'); - expect(helpers.merge(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']); - }); - it('should ignore sources which are not objects', function() { - expect(helpers.merge({a: 42})).toEqual({a: 42}); - expect(helpers.merge({a: 42}, null)).toEqual({a: 42}); - expect(helpers.merge({a: 42}, 42)).toEqual({a: 42}); - }); - it('should recursively overwrite target with source properties', function() { - expect(helpers.merge({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}}); - expect(helpers.merge({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 2}}); - expect(helpers.merge({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [3, 4]}); - expect(helpers.merge({a: 42}, {a: {b: 0}})).toEqual({a: {b: 0}}); - expect(helpers.merge({a: 42}, {a: null})).toEqual({a: null}); - expect(helpers.merge({a: 42}, {a: undefined})).toEqual({a: undefined}); - }); - it('should merge multiple sources in the correct order', function() { - var t0 = {a: {b: 1, c: [1, 2]}}; - var s0 = {a: {d: 3}, e: {f: 4}}; - var s1 = {a: {b: 5}}; - var s2 = {a: {c: [6, 7]}, e: 'foo'}; - - expect(helpers.merge(t0, [s0, s1, s2])).toEqual({a: {b: 5, c: [6, 7], d: 3}, e: 'foo'}); - }); - it('should deep copy merged values from sources', function() { - var a0 = ['foo']; - var a1 = [1, 2, 3]; - var o0 = {a: a1, i: 42}; - var output = helpers.merge({}, {a: a0, o: o0}); - - expect(output).toEqual({a: a0, o: o0}); - expect(output.a).not.toBe(a0); - expect(output.o).not.toBe(o0); - expect(output.o.a).not.toBe(a1); - }); - }); - - describe('mergeIf', function() { - it('should not allow prototype pollution', function() { - var test = helpers.mergeIf({}, JSON.parse('{"__proto__":{"polluted": true}}')); - expect(test.prototype).toBeUndefined(); - expect(Object.prototype.polluted).toBeUndefined(); - }); - it('should update target and return it', function() { - var target = {a: 1}; - var result = helpers.mergeIf(target, {a: 2, b: 'foo'}); - expect(target).toEqual({a: 1, b: 'foo'}); - expect(target).toBe(result); - }); - it('should return target if not an object', function() { - expect(helpers.mergeIf(undefined, {a: 42})).toEqual(undefined); - expect(helpers.mergeIf(null, {a: 42})).toEqual(null); - expect(helpers.mergeIf('foo', {a: 42})).toEqual('foo'); - expect(helpers.mergeIf(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']); - }); - it('should ignore sources which are not objects', function() { - expect(helpers.mergeIf({a: 42})).toEqual({a: 42}); - expect(helpers.mergeIf({a: 42}, null)).toEqual({a: 42}); - expect(helpers.mergeIf({a: 42}, 42)).toEqual({a: 42}); - }); - it('should recursively copy source properties in target only if they do not exist in target', function() { - expect(helpers.mergeIf({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}}); - expect(helpers.mergeIf({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 1}}); - expect(helpers.mergeIf({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [1, 2]}); - expect(helpers.mergeIf({a: 0}, {a: {b: 2}})).toEqual({a: 0}); - expect(helpers.mergeIf({a: null}, {a: 42})).toEqual({a: null}); - expect(helpers.mergeIf({a: undefined}, {a: 42})).toEqual({a: undefined}); - }); - it('should merge multiple sources in the correct order', function() { - var t0 = {a: {b: 1, c: [1, 2]}}; - var s0 = {a: {d: 3}, e: {f: 4}}; - var s1 = {a: {b: 5}}; - var s2 = {a: {c: [6, 7]}, e: 'foo'}; - - expect(helpers.mergeIf(t0, [s0, s1, s2])).toEqual({a: {b: 1, c: [1, 2], d: 3}, e: {f: 4}}); - }); - it('should deep copy merged values from sources', function() { - var a0 = ['foo']; - var a1 = [1, 2, 3]; - var o0 = {a: a1, i: 42}; - var output = helpers.mergeIf({}, {a: a0, o: o0}); - - expect(output).toEqual({a: a0, o: o0}); - expect(output.a).not.toBe(a0); - expect(output.o).not.toBe(o0); - expect(output.o.a).not.toBe(a1); - }); - }); - - describe('resolveObjectKey', function() { - it('should resolve empty key to root object', function() { - const obj = {test: true}; - expect(helpers.resolveObjectKey(obj, '')).toEqual(obj); - }); - it('should resolve one level', function() { - const obj = { - bool: true, - str: 'test', - int: 42, - obj: {name: 'object'} - }; - expect(helpers.resolveObjectKey(obj, 'bool')).toEqual(true); - expect(helpers.resolveObjectKey(obj, 'str')).toEqual('test'); - expect(helpers.resolveObjectKey(obj, 'int')).toEqual(42); - expect(helpers.resolveObjectKey(obj, 'obj')).toEqual(obj.obj); - }); - it('should resolve multiple levels', function() { - const obj = { - child: { - level: 1, - child: { - level: 2, - child: { - level: 3 - } - } - } - }; - expect(helpers.resolveObjectKey(obj, 'child.level')).toEqual(1); - expect(helpers.resolveObjectKey(obj, 'child.child.level')).toEqual(2); - expect(helpers.resolveObjectKey(obj, 'child.child.child.level')).toEqual(3); - }); - it('should resolve circular reference', function() { - const root = {}; - const child = {root}; - child.child = child; - root.child = child; - expect(helpers.resolveObjectKey(root, 'child')).toEqual(child); - expect(helpers.resolveObjectKey(root, 'child.child.child.child.child.child')).toEqual(child); - expect(helpers.resolveObjectKey(root, 'child.child.root')).toEqual(root); - }); - it('should break at empty key', function() { - const obj = { - child: { - level: 1, - child: { - level: 2, - child: { - level: 3 - } - } - } - }; - expect(helpers.resolveObjectKey(obj, 'child..level')).toEqual(obj.child); - expect(helpers.resolveObjectKey(obj, 'child.child.level...')).toEqual(2); - expect(helpers.resolveObjectKey(obj, '.')).toEqual(obj); - expect(helpers.resolveObjectKey(obj, '..')).toEqual(obj); - }); - it('should resolve undefined', function() { - const obj = { - child: { - level: 1, - child: { - level: 2, - child: { - level: 3 - } - } - } - }; - expect(helpers.resolveObjectKey(obj, 'level')).toEqual(undefined); - expect(helpers.resolveObjectKey(obj, 'child.level.a')).toEqual(undefined); - }); - it('should throw on invalid input', function() { - expect(() => helpers.resolveObjectKey(undefined, undefined)).toThrow(); - expect(() => helpers.resolveObjectKey({}, null)).toThrow(); - expect(() => helpers.resolveObjectKey({}, false)).toThrow(); - expect(() => helpers.resolveObjectKey({}, true)).toThrow(); - expect(() => helpers.resolveObjectKey({}, 1)).toThrow(); - }); - }); + var helpers = Chart.helpers; + + describe('noop', function() { + it('should be callable', function() { + expect(helpers.noop).toBeDefined(); + expect(typeof helpers.noop).toBe('function'); + expect(typeof helpers.noop.call).toBe('function'); + }); + it('should returns "undefined"', function() { + expect(helpers.noop(42)).not.toBeDefined(); + expect(helpers.noop.call(this, 42)).not.toBeDefined(); + }); + }); + + describe('isArray', function() { + it('should return true if value is an array', function() { + expect(helpers.isArray([])).toBeTruthy(); + expect(helpers.isArray([42])).toBeTruthy(); + expect(helpers.isArray(new Array())).toBeTruthy(); + expect(helpers.isArray(Array.prototype)).toBeTruthy(); + expect(helpers.isArray(new Int8Array(2))).toBeTruthy(); + expect(helpers.isArray(new Uint8Array())).toBeTruthy(); + expect(helpers.isArray(new Uint8ClampedArray([128, 244]))).toBeTruthy(); + expect(helpers.isArray(new Int16Array())).toBeTruthy(); + expect(helpers.isArray(new Uint16Array())).toBeTruthy(); + expect(helpers.isArray(new Int32Array())).toBeTruthy(); + expect(helpers.isArray(new Uint32Array())).toBeTruthy(); + expect(helpers.isArray(new Float32Array([1.2]))).toBeTruthy(); + expect(helpers.isArray(new Float64Array([]))).toBeTruthy(); + }); + it('should return false if value is not an array', function() { + expect(helpers.isArray()).toBeFalsy(); + expect(helpers.isArray({})).toBeFalsy(); + expect(helpers.isArray(undefined)).toBeFalsy(); + expect(helpers.isArray(null)).toBeFalsy(); + expect(helpers.isArray(true)).toBeFalsy(); + expect(helpers.isArray(false)).toBeFalsy(); + expect(helpers.isArray(42)).toBeFalsy(); + expect(helpers.isArray('Array')).toBeFalsy(); + expect(helpers.isArray({__proto__: Array.prototype})).toBeFalsy(); + }); + }); + + describe('isObject', function() { + it('should return true if value is an object', function() { + expect(helpers.isObject({})).toBeTruthy(); + expect(helpers.isObject({a: 42})).toBeTruthy(); + expect(helpers.isObject(new Object())).toBeTruthy(); + }); + it('should return false if value is not an object', function() { + expect(helpers.isObject()).toBeFalsy(); + expect(helpers.isObject(undefined)).toBeFalsy(); + expect(helpers.isObject(null)).toBeFalsy(); + expect(helpers.isObject(true)).toBeFalsy(); + expect(helpers.isObject(false)).toBeFalsy(); + expect(helpers.isObject(42)).toBeFalsy(); + expect(helpers.isObject('Object')).toBeFalsy(); + expect(helpers.isObject([])).toBeFalsy(); + expect(helpers.isObject([42])).toBeFalsy(); + expect(helpers.isObject(new Array())).toBeFalsy(); + expect(helpers.isObject(new Date())).toBeFalsy(); + }); + }); + + 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(); + expect(helpers.isNullOrUndef(undefined)).toBeTruthy(); + }); + it('should return false if value is not null/undefined', function() { + expect(helpers.isNullOrUndef(true)).toBeFalsy(); + expect(helpers.isNullOrUndef(false)).toBeFalsy(); + expect(helpers.isNullOrUndef('')).toBeFalsy(); + expect(helpers.isNullOrUndef('String')).toBeFalsy(); + expect(helpers.isNullOrUndef(0)).toBeFalsy(); + expect(helpers.isNullOrUndef([])).toBeFalsy(); + expect(helpers.isNullOrUndef({})).toBeFalsy(); + expect(helpers.isNullOrUndef([42])).toBeFalsy(); + expect(helpers.isNullOrUndef(new Date())).toBeFalsy(); + }); + }); + + describe('valueOrDefault', function() { + it('should return value if defined', function() { + var object = {}; + var array = []; + + expect(helpers.valueOrDefault(null, 42)).toBe(null); + expect(helpers.valueOrDefault(false, 42)).toBe(false); + expect(helpers.valueOrDefault(object, 42)).toBe(object); + expect(helpers.valueOrDefault(array, 42)).toBe(array); + expect(helpers.valueOrDefault('', 42)).toBe(''); + expect(helpers.valueOrDefault(0, 42)).toBe(0); + }); + it('should return default if undefined', function() { + expect(helpers.valueOrDefault(undefined, 42)).toBe(42); + expect(helpers.valueOrDefault({}.foo, 42)).toBe(42); + }); + }); + + describe('callback', function() { + it('should return undefined if fn is not a function', function() { + expect(helpers.callback()).not.toBeDefined(); + expect(helpers.callback(null)).not.toBeDefined(); + expect(helpers.callback(42)).not.toBeDefined(); + expect(helpers.callback([])).not.toBeDefined(); + expect(helpers.callback({})).not.toBeDefined(); + }); + it('should call fn with the given args', function() { + var spy = jasmine.createSpy('spy'); + helpers.callback(spy); + helpers.callback(spy, []); + helpers.callback(spy, ['foo']); + helpers.callback(spy, [42, 'bar']); + + expect(spy.calls.argsFor(0)).toEqual([]); + expect(spy.calls.argsFor(1)).toEqual([]); + expect(spy.calls.argsFor(2)).toEqual(['foo']); + expect(spy.calls.argsFor(3)).toEqual([42, 'bar']); + }); + it('should call fn with the given scope', function() { + var spy = jasmine.createSpy('spy'); + var scope = {}; + + helpers.callback(spy); + helpers.callback(spy, [], null); + helpers.callback(spy, [], undefined); + helpers.callback(spy, [], scope); + + expect(spy.calls.all()[0].object).toBe(window); + expect(spy.calls.all()[1].object).toBe(window); + expect(spy.calls.all()[2].object).toBe(window); + expect(spy.calls.all()[3].object).toBe(scope); + }); + it('should return the value returned by fn', function() { + expect(helpers.callback(helpers.noop, [41])).toBe(undefined); + expect(helpers.callback(function(i) { + return i + 1; + }, [41])).toBe(42); + }); + }); + + describe('each', function() { + it('should iterate over an array forward if reverse === false', function() { + var scope = {}; + var scopes = []; + var items = []; + var keys = []; + + helpers.each(['foo', 'bar', 42], function(item, key) { + scopes.push(this); + items.push(item); + keys.push(key); + }, scope); + + expect(scopes).toEqual([scope, scope, scope]); + expect(items).toEqual(['foo', 'bar', 42]); + expect(keys).toEqual([0, 1, 2]); + }); + it('should iterate over an array backward if reverse === true', function() { + var scope = {}; + var scopes = []; + var items = []; + var keys = []; + + helpers.each(['foo', 'bar', 42], function(item, key) { + scopes.push(this); + items.push(item); + keys.push(key); + }, scope, true); + + expect(scopes).toEqual([scope, scope, scope]); + expect(items).toEqual([42, 'bar', 'foo']); + expect(keys).toEqual([2, 1, 0]); + }); + it('should iterate over object properties', function() { + var scope = {}; + var scopes = []; + var items = []; + + helpers.each({a: 'foo', b: 'bar', c: 42}, function(item, key) { + scopes.push(this); + items[key] = item; + }, scope); + + expect(scopes).toEqual([scope, scope, scope]); + expect(items).toEqual(jasmine.objectContaining({a: 'foo', b: 'bar', c: 42})); + }); + it('should not throw when called with a non iterable object', function() { + expect(function() { + helpers.each(undefined); + }).not.toThrow(); + expect(function() { + helpers.each(null); + }).not.toThrow(); + expect(function() { + helpers.each(42); + }).not.toThrow(); + }); + }); + + describe('_elementsEqual', function() { + it('should return true if arrays are the same', function() { + expect(helpers._elementsEqual( + [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}], + [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}])).toBeTruthy(); + }); + it('should return false if arrays are not the same', function() { + expect(helpers._elementsEqual([], [{datasetIndex: 0, index: 1}])).toBeFalsy(); + expect(helpers._elementsEqual([{datasetIndex: 0, index: 2}], [{datasetIndex: 0, index: 1}])).toBeFalsy(); + }); + }); + + describe('clone', function() { + it('should clone primitive values', function() { + expect(helpers.clone()).toBe(undefined); + expect(helpers.clone(null)).toBe(null); + expect(helpers.clone(true)).toBe(true); + expect(helpers.clone(42)).toBe(42); + expect(helpers.clone('foo')).toBe('foo'); + }); + it('should perform a deep copy of arrays', function() { + var o0 = {a: 42}; + var o1 = {s: 's'}; + var a0 = ['bar']; + var a1 = [a0, o0, 2]; + var f0 = function() {}; + var input = [a1, o1, f0, 42, 'foo']; + var output = helpers.clone(input); + + expect(output).toEqual(input); + expect(output).not.toBe(input); + expect(output[0]).not.toBe(a1); + expect(output[0][0]).not.toBe(a0); + expect(output[1]).not.toBe(o1); + }); + it('should perform a deep copy of objects', function() { + var a0 = ['bar']; + var a1 = [1, 2, 3]; + var o0 = {a: a1, i: 42}; + var f0 = function() {}; + var input = {o: o0, a: a0, f: f0, s: 'foo', i: 42}; + var output = helpers.clone(input); + + expect(output).toEqual(input); + expect(output).not.toBe(input); + expect(output.o).not.toBe(o0); + expect(output.o.a).not.toBe(a1); + expect(output.a).not.toBe(a0); + }); + }); + + describe('merge', function() { + it('should not allow prototype pollution', function() { + var test = helpers.merge({}, JSON.parse('{"__proto__":{"polluted": true}}')); + expect(test.prototype).toBeUndefined(); + expect(Object.prototype.polluted).toBeUndefined(); + }); + it('should update target and return it', function() { + var target = {a: 1}; + var result = helpers.merge(target, {a: 2, b: 'foo'}); + expect(target).toEqual({a: 2, b: 'foo'}); + expect(target).toBe(result); + }); + it('should return target if not an object', function() { + expect(helpers.merge(undefined, {a: 42})).toEqual(undefined); + expect(helpers.merge(null, {a: 42})).toEqual(null); + expect(helpers.merge('foo', {a: 42})).toEqual('foo'); + expect(helpers.merge(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']); + }); + it('should ignore sources which are not objects', function() { + expect(helpers.merge({a: 42})).toEqual({a: 42}); + expect(helpers.merge({a: 42}, null)).toEqual({a: 42}); + expect(helpers.merge({a: 42}, 42)).toEqual({a: 42}); + }); + it('should recursively overwrite target with source properties', function() { + expect(helpers.merge({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}}); + expect(helpers.merge({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 2}}); + expect(helpers.merge({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [3, 4]}); + expect(helpers.merge({a: 42}, {a: {b: 0}})).toEqual({a: {b: 0}}); + expect(helpers.merge({a: 42}, {a: null})).toEqual({a: null}); + expect(helpers.merge({a: 42}, {a: undefined})).toEqual({a: undefined}); + }); + it('should merge multiple sources in the correct order', function() { + var t0 = {a: {b: 1, c: [1, 2]}}; + var s0 = {a: {d: 3}, e: {f: 4}}; + var s1 = {a: {b: 5}}; + var s2 = {a: {c: [6, 7]}, e: 'foo'}; + + expect(helpers.merge(t0, [s0, s1, s2])).toEqual({a: {b: 5, c: [6, 7], d: 3}, e: 'foo'}); + }); + it('should deep copy merged values from sources', function() { + var a0 = ['foo']; + var a1 = [1, 2, 3]; + var o0 = {a: a1, i: 42}; + var output = helpers.merge({}, {a: a0, o: o0}); + + expect(output).toEqual({a: a0, o: o0}); + expect(output.a).not.toBe(a0); + expect(output.o).not.toBe(o0); + expect(output.o.a).not.toBe(a1); + }); + }); + + describe('mergeIf', function() { + it('should not allow prototype pollution', function() { + var test = helpers.mergeIf({}, JSON.parse('{"__proto__":{"polluted": true}}')); + expect(test.prototype).toBeUndefined(); + expect(Object.prototype.polluted).toBeUndefined(); + }); + it('should update target and return it', function() { + var target = {a: 1}; + var result = helpers.mergeIf(target, {a: 2, b: 'foo'}); + expect(target).toEqual({a: 1, b: 'foo'}); + expect(target).toBe(result); + }); + it('should return target if not an object', function() { + expect(helpers.mergeIf(undefined, {a: 42})).toEqual(undefined); + expect(helpers.mergeIf(null, {a: 42})).toEqual(null); + expect(helpers.mergeIf('foo', {a: 42})).toEqual('foo'); + expect(helpers.mergeIf(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']); + }); + it('should ignore sources which are not objects', function() { + expect(helpers.mergeIf({a: 42})).toEqual({a: 42}); + expect(helpers.mergeIf({a: 42}, null)).toEqual({a: 42}); + expect(helpers.mergeIf({a: 42}, 42)).toEqual({a: 42}); + }); + it('should recursively copy source properties in target only if they do not exist in target', function() { + expect(helpers.mergeIf({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}}); + expect(helpers.mergeIf({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 1}}); + expect(helpers.mergeIf({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [1, 2]}); + expect(helpers.mergeIf({a: 0}, {a: {b: 2}})).toEqual({a: 0}); + expect(helpers.mergeIf({a: null}, {a: 42})).toEqual({a: null}); + expect(helpers.mergeIf({a: undefined}, {a: 42})).toEqual({a: undefined}); + }); + it('should merge multiple sources in the correct order', function() { + var t0 = {a: {b: 1, c: [1, 2]}}; + var s0 = {a: {d: 3}, e: {f: 4}}; + var s1 = {a: {b: 5}}; + var s2 = {a: {c: [6, 7]}, e: 'foo'}; + + expect(helpers.mergeIf(t0, [s0, s1, s2])).toEqual({a: {b: 1, c: [1, 2], d: 3}, e: {f: 4}}); + }); + it('should deep copy merged values from sources', function() { + var a0 = ['foo']; + var a1 = [1, 2, 3]; + var o0 = {a: a1, i: 42}; + var output = helpers.mergeIf({}, {a: a0, o: o0}); + + expect(output).toEqual({a: a0, o: o0}); + expect(output.a).not.toBe(a0); + expect(output.o).not.toBe(o0); + expect(output.o.a).not.toBe(a1); + }); + }); + + describe('resolveObjectKey', function() { + it('should resolve empty key to root object', function() { + const obj = {test: true}; + expect(helpers.resolveObjectKey(obj, '')).toEqual(obj); + }); + it('should resolve one level', function() { + const obj = { + bool: true, + str: 'test', + int: 42, + obj: {name: 'object'} + }; + expect(helpers.resolveObjectKey(obj, 'bool')).toEqual(true); + expect(helpers.resolveObjectKey(obj, 'str')).toEqual('test'); + expect(helpers.resolveObjectKey(obj, 'int')).toEqual(42); + expect(helpers.resolveObjectKey(obj, 'obj')).toEqual(obj.obj); + }); + it('should resolve multiple levels', function() { + const obj = { + child: { + level: 1, + child: { + level: 2, + child: { + level: 3 + } + } + } + }; + expect(helpers.resolveObjectKey(obj, 'child.level')).toEqual(1); + expect(helpers.resolveObjectKey(obj, 'child.child.level')).toEqual(2); + expect(helpers.resolveObjectKey(obj, 'child.child.child.level')).toEqual(3); + }); + it('should resolve circular reference', function() { + const root = {}; + const child = {root}; + child.child = child; + root.child = child; + expect(helpers.resolveObjectKey(root, 'child')).toEqual(child); + expect(helpers.resolveObjectKey(root, 'child.child.child.child.child.child')).toEqual(child); + expect(helpers.resolveObjectKey(root, 'child.child.root')).toEqual(root); + }); + it('should break at empty key', function() { + const obj = { + child: { + level: 1, + child: { + level: 2, + child: { + level: 3 + } + } + } + }; + expect(helpers.resolveObjectKey(obj, 'child..level')).toEqual(obj.child); + expect(helpers.resolveObjectKey(obj, 'child.child.level...')).toEqual(2); + expect(helpers.resolveObjectKey(obj, '.')).toEqual(obj); + expect(helpers.resolveObjectKey(obj, '..')).toEqual(obj); + }); + it('should resolve undefined', function() { + const obj = { + child: { + level: 1, + child: { + level: 2, + child: { + level: 3 + } + } + } + }; + expect(helpers.resolveObjectKey(obj, 'level')).toEqual(undefined); + expect(helpers.resolveObjectKey(obj, 'child.level.a')).toEqual(undefined); + }); + it('should throw on invalid input', function() { + expect(() => helpers.resolveObjectKey(undefined, undefined)).toThrow(); + expect(() => helpers.resolveObjectKey({}, null)).toThrow(); + expect(() => helpers.resolveObjectKey({}, false)).toThrow(); + expect(() => helpers.resolveObjectKey({}, true)).toThrow(); + expect(() => helpers.resolveObjectKey({}, 1)).toThrow(); + }); + }); }); diff --git a/test/specs/helpers.curve.tests.js b/test/specs/helpers.curve.tests.js index 1b888e986f2..25a447befd7 100644 --- a/test/specs/helpers.curve.tests.js +++ b/test/specs/helpers.curve.tests.js @@ -1,183 +1,183 @@ describe('Curve helper tests', function() { - let helpers; + let helpers; - beforeAll(function() { - helpers = window.Chart.helpers; - }); + beforeAll(function() { + helpers = window.Chart.helpers; + }); - it('should spline curves', function() { - expect(helpers.splineCurve({ - x: 0, - y: 0 - }, { - x: 1, - y: 1 - }, { - x: 2, - y: 0 - }, 0)).toEqual({ - previous: { - x: 1, - y: 1, - }, - next: { - x: 1, - y: 1, - } - }); + it('should spline curves', function() { + expect(helpers.splineCurve({ + x: 0, + y: 0 + }, { + x: 1, + y: 1 + }, { + x: 2, + y: 0 + }, 0)).toEqual({ + previous: { + x: 1, + y: 1, + }, + next: { + x: 1, + y: 1, + } + }); - expect(helpers.splineCurve({ - x: 0, - y: 0 - }, { - x: 1, - y: 1 - }, { - x: 2, - y: 0 - }, 1)).toEqual({ - previous: { - x: 0, - y: 1, - }, - next: { - x: 2, - y: 1, - } - }); - }); + expect(helpers.splineCurve({ + x: 0, + y: 0 + }, { + x: 1, + y: 1 + }, { + x: 2, + y: 0 + }, 1)).toEqual({ + previous: { + x: 0, + y: 1, + }, + next: { + x: 2, + y: 1, + } + }); + }); - it('should spline curves with monotone cubic interpolation', function() { - var dataPoints = [ - {x: 0, y: 0, skip: false}, - {x: 3, y: 6, skip: false}, - {x: 9, y: 6, skip: false}, - {x: 12, y: 60, skip: false}, - {x: 15, y: 60, skip: false}, - {x: 18, y: 120, skip: false}, - {x: null, y: null, skip: true}, - {x: 21, y: 180, skip: false}, - {x: 24, y: 120, skip: false}, - {x: 27, y: 125, skip: false}, - {x: 30, y: 105, skip: false}, - {x: 33, y: 110, skip: false}, - {x: 33, y: 110, skip: false}, - {x: 36, y: 170, skip: false} - ]; - helpers.splineCurveMonotone(dataPoints); - expect(dataPoints).toEqual([{ - x: 0, - y: 0, - skip: false, - controlPointNextX: 1, - controlPointNextY: 2 - }, - { - x: 3, - y: 6, - skip: false, - controlPointPreviousX: 2, - controlPointPreviousY: 6, - controlPointNextX: 5, - controlPointNextY: 6 - }, - { - x: 9, - y: 6, - skip: false, - controlPointPreviousX: 7, - controlPointPreviousY: 6, - controlPointNextX: 10, - controlPointNextY: 6 - }, - { - x: 12, - y: 60, - skip: false, - controlPointPreviousX: 11, - controlPointPreviousY: 60, - controlPointNextX: 13, - controlPointNextY: 60 - }, - { - x: 15, - y: 60, - skip: false, - controlPointPreviousX: 14, - controlPointPreviousY: 60, - controlPointNextX: 16, - controlPointNextY: 60 - }, - { - x: 18, - y: 120, - skip: false, - controlPointPreviousX: 17, - controlPointPreviousY: 100 - }, - { - x: null, - y: null, - skip: true - }, - { - x: 21, - y: 180, - skip: false, - controlPointNextX: 22, - controlPointNextY: 160 - }, - { - x: 24, - y: 120, - skip: false, - controlPointPreviousX: 23, - controlPointPreviousY: 120, - controlPointNextX: 25, - controlPointNextY: 120 - }, - { - x: 27, - y: 125, - skip: false, - controlPointPreviousX: 26, - controlPointPreviousY: 125, - controlPointNextX: 28, - controlPointNextY: 125 - }, - { - x: 30, - y: 105, - skip: false, - controlPointPreviousX: 29, - controlPointPreviousY: 105, - controlPointNextX: 31, - controlPointNextY: 105 - }, - { - x: 33, - y: 110, - skip: false, - controlPointPreviousX: 32, - controlPointPreviousY: 110, - controlPointNextX: 33, - controlPointNextY: 110 - }, - { - x: 33, - y: 110, - skip: false, - controlPointPreviousX: 33, - controlPointPreviousY: 110, - controlPointNextX: 34, - controlPointNextY: 110 - }, - { - x: 36, - y: 170, - skip: false, - controlPointPreviousX: 35, - controlPointPreviousY: 150 - }]); - }); + it('should spline curves with monotone cubic interpolation', function() { + var dataPoints = [ + {x: 0, y: 0, skip: false}, + {x: 3, y: 6, skip: false}, + {x: 9, y: 6, skip: false}, + {x: 12, y: 60, skip: false}, + {x: 15, y: 60, skip: false}, + {x: 18, y: 120, skip: false}, + {x: null, y: null, skip: true}, + {x: 21, y: 180, skip: false}, + {x: 24, y: 120, skip: false}, + {x: 27, y: 125, skip: false}, + {x: 30, y: 105, skip: false}, + {x: 33, y: 110, skip: false}, + {x: 33, y: 110, skip: false}, + {x: 36, y: 170, skip: false} + ]; + helpers.splineCurveMonotone(dataPoints); + expect(dataPoints).toEqual([{ + x: 0, + y: 0, + skip: false, + controlPointNextX: 1, + controlPointNextY: 2 + }, + { + x: 3, + y: 6, + skip: false, + controlPointPreviousX: 2, + controlPointPreviousY: 6, + controlPointNextX: 5, + controlPointNextY: 6 + }, + { + x: 9, + y: 6, + skip: false, + controlPointPreviousX: 7, + controlPointPreviousY: 6, + controlPointNextX: 10, + controlPointNextY: 6 + }, + { + x: 12, + y: 60, + skip: false, + controlPointPreviousX: 11, + controlPointPreviousY: 60, + controlPointNextX: 13, + controlPointNextY: 60 + }, + { + x: 15, + y: 60, + skip: false, + controlPointPreviousX: 14, + controlPointPreviousY: 60, + controlPointNextX: 16, + controlPointNextY: 60 + }, + { + x: 18, + y: 120, + skip: false, + controlPointPreviousX: 17, + controlPointPreviousY: 100 + }, + { + x: null, + y: null, + skip: true + }, + { + x: 21, + y: 180, + skip: false, + controlPointNextX: 22, + controlPointNextY: 160 + }, + { + x: 24, + y: 120, + skip: false, + controlPointPreviousX: 23, + controlPointPreviousY: 120, + controlPointNextX: 25, + controlPointNextY: 120 + }, + { + x: 27, + y: 125, + skip: false, + controlPointPreviousX: 26, + controlPointPreviousY: 125, + controlPointNextX: 28, + controlPointNextY: 125 + }, + { + x: 30, + y: 105, + skip: false, + controlPointPreviousX: 29, + controlPointPreviousY: 105, + controlPointNextX: 31, + controlPointNextY: 105 + }, + { + x: 33, + y: 110, + skip: false, + controlPointPreviousX: 32, + controlPointPreviousY: 110, + controlPointNextX: 33, + controlPointNextY: 110 + }, + { + x: 33, + y: 110, + skip: false, + controlPointPreviousX: 33, + controlPointPreviousY: 110, + controlPointNextX: 34, + controlPointNextY: 110 + }, + { + x: 36, + y: 170, + skip: false, + controlPointPreviousX: 35, + controlPointPreviousY: 150 + }]); + }); }); diff --git a/test/specs/helpers.dom.tests.js b/test/specs/helpers.dom.tests.js index 9cd370d0558..d3b3f4c6bf3 100644 --- a/test/specs/helpers.dom.tests.js +++ b/test/specs/helpers.dom.tests.js @@ -1,403 +1,403 @@ describe('DOM helpers tests', function() { - let helpers; + let helpers; - beforeAll(function() { - helpers = window.Chart.helpers; - }); + beforeAll(function() { + helpers = window.Chart.helpers; + }); - it ('should get the maximum size for a node', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum size for a node', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create the div we want to get the max size for - var innerDiv = document.createElement('div'); - div.appendChild(innerDiv); + // Create the div we want to get the max size for + var innerDiv = document.createElement('div'); + div.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum width and height for a node in a ShadowRoot', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum width and height for a node in a ShadowRoot', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - if (!div.attachShadow) { - // Shadow DOM is not natively supported - return; - } + if (!div.attachShadow) { + // Shadow DOM is not natively supported + return; + } - var shadow = div.attachShadow({mode: 'closed'}); + var shadow = div.attachShadow({mode: 'closed'}); - // Create the div we want to get the max size for - var innerDiv = document.createElement('div'); - shadow.appendChild(innerDiv); + // Create the div we want to get the max size for + var innerDiv = document.createElement('div'); + shadow.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum width of a node that has a max-width style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum width of a node that has a max-width style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create the div we want to get the max size for and set a max-width style - var innerDiv = document.createElement('div'); - innerDiv.style.maxWidth = '150px'; - div.appendChild(innerDiv); + // Create the div we want to get the max size for and set a max-width style + var innerDiv = document.createElement('div'); + innerDiv.style.maxWidth = '150px'; + div.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum height of a node that has a max-height style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum height of a node that has a max-height style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create the div we want to get the max size for and set a max-height style - var innerDiv = document.createElement('div'); - innerDiv.style.maxHeight = '150px'; - div.appendChild(innerDiv); + // Create the div we want to get the max size for and set a max-height style + var innerDiv = document.createElement('div'); + innerDiv.style.maxHeight = '150px'; + div.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum width of a node when the parent has a max-width style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum width of a node when the parent has a max-width style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create an inner wrapper around our div we want to size and give that a max-width style - var parentDiv = document.createElement('div'); - parentDiv.style.maxWidth = '150px'; - div.appendChild(parentDiv); + // Create an inner wrapper around our div we want to size and give that a max-width style + var parentDiv = document.createElement('div'); + parentDiv.style.maxWidth = '150px'; + div.appendChild(parentDiv); - // Create the div we want to get the max size for - var innerDiv = document.createElement('div'); - parentDiv.appendChild(innerDiv); + // Create the div we want to get the max size for + var innerDiv = document.createElement('div'); + parentDiv.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum height of a node when the parent has a max-height style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum height of a node when the parent has a max-height style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create an inner wrapper around our div we want to size and give that a max-height style - var parentDiv = document.createElement('div'); - parentDiv.style.maxHeight = '150px'; - div.appendChild(parentDiv); + // Create an inner wrapper around our div we want to size and give that a max-height style + var parentDiv = document.createElement('div'); + parentDiv.style.maxHeight = '150px'; + div.appendChild(parentDiv); - // Create the div we want to get the max size for - var innerDiv = document.createElement('div'); - innerDiv.style.height = '300px'; // make it large - parentDiv.appendChild(innerDiv); + // Create the div we want to get the max size for + var innerDiv = document.createElement('div'); + innerDiv.style.height = '300px'; // make it large + parentDiv.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum width of a node that has a percentage max-width style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum width of a node that has a percentage max-width style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create the div we want to get the max size for and set a max-width style - var innerDiv = document.createElement('div'); - innerDiv.style.maxWidth = '50%'; - div.appendChild(innerDiv); + // Create the div we want to get the max size for and set a max-width style + var innerDiv = document.createElement('div'); + innerDiv.style.maxWidth = '50%'; + div.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it('should get the maximum height of a node that has a percentage max-height style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it('should get the maximum height of a node that has a percentage max-height style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create the div we want to get the max size for and set a max-height style - var innerDiv = document.createElement('div'); - innerDiv.style.maxHeight = '50%'; - div.appendChild(innerDiv); + // Create the div we want to get the max size for and set a max-height style + var innerDiv = document.createElement('div'); + innerDiv.style.maxHeight = '50%'; + div.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); - document.body.removeChild(div); - }); + document.body.removeChild(div); + }); - it ('should get the maximum width of a node when the parent has a percentage max-width style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; + it ('should get the maximum width of a node when the parent has a percentage max-width style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; - document.body.appendChild(div); + document.body.appendChild(div); - // Create an inner wrapper around our div we want to size and give that a max-width style - var parentDiv = document.createElement('div'); - parentDiv.style.maxWidth = '50%'; - div.appendChild(parentDiv); + // Create an inner wrapper around our div we want to size and give that a max-width style + var parentDiv = document.createElement('div'); + parentDiv.style.maxWidth = '50%'; + div.appendChild(parentDiv); - // Create the div we want to get the max size for - var innerDiv = document.createElement('div'); - parentDiv.appendChild(innerDiv); + // Create the div we want to get the max size for + var innerDiv = document.createElement('div'); + parentDiv.appendChild(innerDiv); - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100})); + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100})); - document.body.removeChild(div); - }); - - it ('should get the maximum height of a node when the parent has a percentage max-height style', function() { - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.height = '300px'; - - document.body.appendChild(div); - - // Create an inner wrapper around our div we want to size and give that a max-height style - var parentDiv = document.createElement('div'); - parentDiv.style.maxHeight = '50%'; - div.appendChild(parentDiv); - - var innerDiv = document.createElement('div'); - innerDiv.style.height = '300px'; // make it large - parentDiv.appendChild(innerDiv); - - expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); - - document.body.removeChild(div); - }); - - it ('Should get padding of parent as number (pixels) when defined as percent (returns incorrectly in IE11)', function() { - - // Create div with fixed size as a test bed - var div = document.createElement('div'); - div.style.width = '300px'; - div.style.height = '300px'; - document.body.appendChild(div); - - // Inner DIV to have 5% padding of parent - var innerDiv = document.createElement('div'); - - div.appendChild(innerDiv); - - var canvas = document.createElement('canvas'); - innerDiv.appendChild(canvas); - - // No padding - expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 300})); - - // test with percentage - innerDiv.style.padding = '5%'; - expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 270})); - - // test with pixels - innerDiv.style.padding = '10px'; - expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 280})); - - document.body.removeChild(div); - }); - - it ('should leave styled height and width on canvas if explicitly set', function() { - var chart = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'height: 400px; width: 400px;' - } - }); - - helpers.retinaScale(chart, true); - - var canvas = chart.canvas; - - expect(canvas.style.height).toBe('400px'); - expect(canvas.style.width).toBe('400px'); - }); - - describe('getRelativePosition', function() { - it('should use offsetX/Y when available', function() { - const event = {offsetX: 50, offsetY: 100}; - const chart = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - } - }); - expect(helpers.getRelativePosition(event, chart)).toEqual({x: 50, y: 100}); - - const chart2 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'padding: 10px' - } - }); - expect(helpers.getRelativePosition(event, chart2)).toEqual({ - x: Math.round((event.offsetX - 10) / 180 * 200), - y: Math.round((event.offsetY - 10) / 180 * 200) - }); - - const chart3 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'width: 400px, height: 400px; padding: 10px' - } - }); - expect(helpers.getRelativePosition(event, chart3)).toEqual({ - x: Math.round((event.offsetX - 10) / 360 * 400), - y: Math.round((event.offsetY - 10) / 360 * 400) - }); - - const chart4 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'width: 400px, height: 400px; padding: 10px; position: absolute; left: 20, top: 20' - } - }); - expect(helpers.getRelativePosition(event, chart4)).toEqual({ - x: Math.round((event.offsetX - 10) / 360 * 400), - y: Math.round((event.offsetY - 10) / 360 * 400) - }); - - }); - - it('should calculate from clientX/Y as fallback', function() { - const chart = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - } - }); - - const event = { - clientX: 50, - clientY: 100 - }; - - const rect = chart.canvas.getBoundingClientRect(); - const pos = helpers.getRelativePosition(event, chart); - expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1); - expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1); - - const chart2 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'padding: 10px' - } - }); - const rect2 = chart2.canvas.getBoundingClientRect(); - const pos2 = helpers.getRelativePosition(event, chart2); - expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1); - expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1); - - const chart3 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'width: 400px, height: 400px; padding: 10px' - } - }); - const rect3 = chart3.canvas.getBoundingClientRect(); - const pos3 = helpers.getRelativePosition(event, chart3); - expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1); - expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1); - }); - - it ('should get the correct relative position for a node in a ShadowRoot', function() { - const event = { - offsetX: 50, - offsetY: 100, - clientX: 50, - clientY: 100 - }; - - const chart = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - }, - useShadowDOM: true - }); - - event.target = chart.canvas.parentNode.host; - expect(event.target.shadowRoot).not.toEqual(null); - const rect = chart.canvas.getBoundingClientRect(); - const pos = helpers.getRelativePosition(event, chart); - expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1); - expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1); - - const chart2 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'padding: 10px' - }, - useShadowDOM: true - }); - - event.target = chart2.canvas.parentNode.host; - const rect2 = chart2.canvas.getBoundingClientRect(); - const pos2 = helpers.getRelativePosition(event, chart2); - expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1); - expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1); - - const chart3 = window.acquireChart({}, { - canvas: { - height: 200, - width: 200, - style: 'width: 400px, height: 400px; padding: 10px' - }, - useShadowDOM: true - }); - - event.target = chart3.canvas.parentNode.host; - const rect3 = chart3.canvas.getBoundingClientRect(); - const pos3 = helpers.getRelativePosition(event, chart3); - expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1); - expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1); - }); - }); + document.body.removeChild(div); + }); + + it ('should get the maximum height of a node when the parent has a percentage max-height style', function() { + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.height = '300px'; + + document.body.appendChild(div); + + // Create an inner wrapper around our div we want to size and give that a max-height style + var parentDiv = document.createElement('div'); + parentDiv.style.maxHeight = '50%'; + div.appendChild(parentDiv); + + var innerDiv = document.createElement('div'); + innerDiv.style.height = '300px'; // make it large + parentDiv.appendChild(innerDiv); + + expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150})); + + document.body.removeChild(div); + }); + + it ('Should get padding of parent as number (pixels) when defined as percent (returns incorrectly in IE11)', function() { + + // Create div with fixed size as a test bed + var div = document.createElement('div'); + div.style.width = '300px'; + div.style.height = '300px'; + document.body.appendChild(div); + + // Inner DIV to have 5% padding of parent + var innerDiv = document.createElement('div'); + + div.appendChild(innerDiv); + + var canvas = document.createElement('canvas'); + innerDiv.appendChild(canvas); + + // No padding + expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 300})); + + // test with percentage + innerDiv.style.padding = '5%'; + expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 270})); + + // test with pixels + innerDiv.style.padding = '10px'; + expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 280})); + + document.body.removeChild(div); + }); + + it ('should leave styled height and width on canvas if explicitly set', function() { + var chart = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'height: 400px; width: 400px;' + } + }); + + helpers.retinaScale(chart, true); + + var canvas = chart.canvas; + + expect(canvas.style.height).toBe('400px'); + expect(canvas.style.width).toBe('400px'); + }); + + describe('getRelativePosition', function() { + it('should use offsetX/Y when available', function() { + const event = {offsetX: 50, offsetY: 100}; + const chart = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + } + }); + expect(helpers.getRelativePosition(event, chart)).toEqual({x: 50, y: 100}); + + const chart2 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'padding: 10px' + } + }); + expect(helpers.getRelativePosition(event, chart2)).toEqual({ + x: Math.round((event.offsetX - 10) / 180 * 200), + y: Math.round((event.offsetY - 10) / 180 * 200) + }); + + const chart3 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'width: 400px, height: 400px; padding: 10px' + } + }); + expect(helpers.getRelativePosition(event, chart3)).toEqual({ + x: Math.round((event.offsetX - 10) / 360 * 400), + y: Math.round((event.offsetY - 10) / 360 * 400) + }); + + const chart4 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'width: 400px, height: 400px; padding: 10px; position: absolute; left: 20, top: 20' + } + }); + expect(helpers.getRelativePosition(event, chart4)).toEqual({ + x: Math.round((event.offsetX - 10) / 360 * 400), + y: Math.round((event.offsetY - 10) / 360 * 400) + }); + + }); + + it('should calculate from clientX/Y as fallback', function() { + const chart = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + } + }); + + const event = { + clientX: 50, + clientY: 100 + }; + + const rect = chart.canvas.getBoundingClientRect(); + const pos = helpers.getRelativePosition(event, chart); + expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1); + expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1); + + const chart2 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'padding: 10px' + } + }); + const rect2 = chart2.canvas.getBoundingClientRect(); + const pos2 = helpers.getRelativePosition(event, chart2); + expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1); + expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1); + + const chart3 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'width: 400px, height: 400px; padding: 10px' + } + }); + const rect3 = chart3.canvas.getBoundingClientRect(); + const pos3 = helpers.getRelativePosition(event, chart3); + expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1); + expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1); + }); + + it ('should get the correct relative position for a node in a ShadowRoot', function() { + const event = { + offsetX: 50, + offsetY: 100, + clientX: 50, + clientY: 100 + }; + + const chart = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + }, + useShadowDOM: true + }); + + event.target = chart.canvas.parentNode.host; + expect(event.target.shadowRoot).not.toEqual(null); + const rect = chart.canvas.getBoundingClientRect(); + const pos = helpers.getRelativePosition(event, chart); + expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1); + expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1); + + const chart2 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'padding: 10px' + }, + useShadowDOM: true + }); + + event.target = chart2.canvas.parentNode.host; + const rect2 = chart2.canvas.getBoundingClientRect(); + const pos2 = helpers.getRelativePosition(event, chart2); + expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1); + expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1); + + const chart3 = window.acquireChart({}, { + canvas: { + height: 200, + width: 200, + style: 'width: 400px, height: 400px; padding: 10px' + }, + useShadowDOM: true + }); + + event.target = chart3.canvas.parentNode.host; + const rect3 = chart3.canvas.getBoundingClientRect(); + const pos3 = helpers.getRelativePosition(event, chart3); + expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1); + expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1); + }); + }); }); diff --git a/test/specs/helpers.easing.tests.js b/test/specs/helpers.easing.tests.js index cf5ac99c3e6..cae4ca98488 100644 --- a/test/specs/helpers.easing.tests.js +++ b/test/specs/helpers.easing.tests.js @@ -1,61 +1,61 @@ 'use strict'; describe('Chart.helpers.easingEffects', function() { - var helpers = Chart.helpers; + var helpers = Chart.helpers; - describe('effects', function() { - var expected = { - easeInOutBack: [-0, -0.03751855, -0.09255566, -0.07883348, 0.08992579, 0.5, 0.91007421, 1.07883348, 1.09255566, 1.03751855, 1], - easeInOutBounce: [0, 0.03, 0.11375, 0.045, 0.34875, 0.5, 0.65125, 0.955, 0.88625, 0.97, 1], - easeInOutCirc: [-0, 0.01010205, 0.04174243, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95825757, 0.98989795, 1], - easeInOutCubic: [0, 0.004, 0.032, 0.108, 0.256, 0.5, 0.744, 0.892, 0.968, 0.996, 1], - easeInOutElastic: [0, 0.00033916, -0.00390625, 0.02393889, -0.11746158, 0.5, 1.11746158, 0.97606111, 1.00390625, 0.99966084, 1], - easeInOutExpo: [0, 0.00195313, 0.0078125, 0.03125, 0.125, 0.5, 0.875, 0.96875, 0.9921875, 0.99804688, 1], - easeInOutQuad: [0, 0.02, 0.08, 0.18, 0.32, 0.5, 0.68, 0.82, 0.92, 0.98, 1], - easeInOutQuart: [0, 0.0008, 0.0128, 0.0648, 0.2048, 0.5, 0.7952, 0.9352, 0.9872, 0.9992, 1], - easeInOutQuint: [0, 0.00016, 0.00512, 0.03888, 0.16384, 0.5, 0.83616, 0.96112, 0.99488, 0.99984, 1], - easeInOutSine: [-0, 0.02447174, 0.0954915, 0.20610737, 0.3454915, 0.5, 0.6545085, 0.79389263, 0.9045085, 0.97552826, 1], - easeInBack: [-0, -0.01431422, -0.04645056, -0.08019954, -0.09935168, -0.0876975, -0.02902752, 0.09286774, 0.29419776, 0.59117202, 1], - easeInBounce: [0, 0.011875, 0.06, 0.069375, 0.2275, 0.234375, 0.09, 0.319375, 0.6975, 0.924375, 1], - easeInCirc: [-0, 0.00501256, 0.0202041, 0.0460608, 0.08348486, 0.1339746, 0.2, 0.28585716, 0.4, 0.56411011, 1], - easeInCubic: [0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1], - easeInExpo: [0, 0.00195313, 0.00390625, 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1], - easeInElastic: [0, 0.00195313, -0.00195313, -0.00390625, 0.015625, -0.015625, -0.03125, 0.125, -0.125, -0.25, 1], - easeInQuad: [0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1], - easeInQuart: [0, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401, 0.4096, 0.6561, 1], - easeInQuint: [0, 0.00001, 0.00032, 0.00243, 0.01024, 0.03125, 0.07776, 0.16807, 0.32768, 0.59049, 1], - easeInSine: [0, 0.01231166, 0.04894348, 0.10899348, 0.19098301, 0.29289322, 0.41221475, 0.5460095, 0.69098301, 0.84356553, 1], - easeOutBack: [0, 0.40882798, 0.70580224, 0.90713226, 1.02902752, 1.0876975, 1.09935168, 1.08019954, 1.04645056, 1.01431422, 1], - easeOutBounce: [0, 0.075625, 0.3025, 0.680625, 0.91, 0.765625, 0.7725, 0.930625, 0.94, 0.988125, 1], - easeOutCirc: [0, 0.43588989, 0.6, 0.71414284, 0.8, 0.8660254, 0.91651514, 0.9539392, 0.9797959, 0.99498744, 1], - easeOutElastic: [0, 1.25, 1.125, 0.875, 1.03125, 1.015625, 0.984375, 1.00390625, 1.00195313, 0.99804688, 1], - easeOutExpo: [0, 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.9921875, 0.99609375, 0.99804688, 1], - easeOutCubic: [0, 0.271, 0.488, 0.657, 0.784, 0.875, 0.936, 0.973, 0.992, 0.999, 1], - easeOutQuad: [0, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1], - easeOutQuart: [-0, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919, 0.9984, 0.9999, 1], - easeOutQuint: [0, 0.40951, 0.67232, 0.83193, 0.92224, 0.96875, 0.98976, 0.99757, 0.99968, 0.99999, 1], - easeOutSine: [0, 0.15643447, 0.30901699, 0.4539905, 0.58778525, 0.70710678, 0.80901699, 0.89100652, 0.95105652, 0.98768834, 1], - linear: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] - }; + describe('effects', function() { + var expected = { + easeInOutBack: [-0, -0.03751855, -0.09255566, -0.07883348, 0.08992579, 0.5, 0.91007421, 1.07883348, 1.09255566, 1.03751855, 1], + easeInOutBounce: [0, 0.03, 0.11375, 0.045, 0.34875, 0.5, 0.65125, 0.955, 0.88625, 0.97, 1], + easeInOutCirc: [-0, 0.01010205, 0.04174243, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95825757, 0.98989795, 1], + easeInOutCubic: [0, 0.004, 0.032, 0.108, 0.256, 0.5, 0.744, 0.892, 0.968, 0.996, 1], + easeInOutElastic: [0, 0.00033916, -0.00390625, 0.02393889, -0.11746158, 0.5, 1.11746158, 0.97606111, 1.00390625, 0.99966084, 1], + easeInOutExpo: [0, 0.00195313, 0.0078125, 0.03125, 0.125, 0.5, 0.875, 0.96875, 0.9921875, 0.99804688, 1], + easeInOutQuad: [0, 0.02, 0.08, 0.18, 0.32, 0.5, 0.68, 0.82, 0.92, 0.98, 1], + easeInOutQuart: [0, 0.0008, 0.0128, 0.0648, 0.2048, 0.5, 0.7952, 0.9352, 0.9872, 0.9992, 1], + easeInOutQuint: [0, 0.00016, 0.00512, 0.03888, 0.16384, 0.5, 0.83616, 0.96112, 0.99488, 0.99984, 1], + easeInOutSine: [-0, 0.02447174, 0.0954915, 0.20610737, 0.3454915, 0.5, 0.6545085, 0.79389263, 0.9045085, 0.97552826, 1], + easeInBack: [-0, -0.01431422, -0.04645056, -0.08019954, -0.09935168, -0.0876975, -0.02902752, 0.09286774, 0.29419776, 0.59117202, 1], + easeInBounce: [0, 0.011875, 0.06, 0.069375, 0.2275, 0.234375, 0.09, 0.319375, 0.6975, 0.924375, 1], + easeInCirc: [-0, 0.00501256, 0.0202041, 0.0460608, 0.08348486, 0.1339746, 0.2, 0.28585716, 0.4, 0.56411011, 1], + easeInCubic: [0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1], + easeInExpo: [0, 0.00195313, 0.00390625, 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1], + easeInElastic: [0, 0.00195313, -0.00195313, -0.00390625, 0.015625, -0.015625, -0.03125, 0.125, -0.125, -0.25, 1], + easeInQuad: [0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1], + easeInQuart: [0, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401, 0.4096, 0.6561, 1], + easeInQuint: [0, 0.00001, 0.00032, 0.00243, 0.01024, 0.03125, 0.07776, 0.16807, 0.32768, 0.59049, 1], + easeInSine: [0, 0.01231166, 0.04894348, 0.10899348, 0.19098301, 0.29289322, 0.41221475, 0.5460095, 0.69098301, 0.84356553, 1], + easeOutBack: [0, 0.40882798, 0.70580224, 0.90713226, 1.02902752, 1.0876975, 1.09935168, 1.08019954, 1.04645056, 1.01431422, 1], + easeOutBounce: [0, 0.075625, 0.3025, 0.680625, 0.91, 0.765625, 0.7725, 0.930625, 0.94, 0.988125, 1], + easeOutCirc: [0, 0.43588989, 0.6, 0.71414284, 0.8, 0.8660254, 0.91651514, 0.9539392, 0.9797959, 0.99498744, 1], + easeOutElastic: [0, 1.25, 1.125, 0.875, 1.03125, 1.015625, 0.984375, 1.00390625, 1.00195313, 0.99804688, 1], + easeOutExpo: [0, 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.9921875, 0.99609375, 0.99804688, 1], + easeOutCubic: [0, 0.271, 0.488, 0.657, 0.784, 0.875, 0.936, 0.973, 0.992, 0.999, 1], + easeOutQuad: [0, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1], + easeOutQuart: [-0, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919, 0.9984, 0.9999, 1], + easeOutQuint: [0, 0.40951, 0.67232, 0.83193, 0.92224, 0.96875, 0.98976, 0.99757, 0.99968, 0.99999, 1], + easeOutSine: [0, 0.15643447, 0.30901699, 0.4539905, 0.58778525, 0.70710678, 0.80901699, 0.89100652, 0.95105652, 0.98768834, 1], + linear: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] + }; - function generate(method) { - var fn = helpers.easingEffects[method]; - var accuracy = Math.pow(10, 8); - var count = 10; - var values = []; - var i; + function generate(method) { + var fn = helpers.easingEffects[method]; + var accuracy = Math.pow(10, 8); + var count = 10; + var values = []; + var i; - for (i = 0; i <= count; ++i) { - values.push(Math.round(accuracy * fn(i / count)) / accuracy); - } + for (i = 0; i <= count; ++i) { + values.push(Math.round(accuracy * fn(i / count)) / accuracy); + } - return values; - } + return values; + } - Object.keys(helpers.easingEffects).forEach(function(method) { - it ('"' + method + '" should return expected values', function() { - expect(generate(method)).toEqual(expected[method]); - }); - }); - }); + Object.keys(helpers.easingEffects).forEach(function(method) { + it ('"' + method + '" should return expected values', function() { + expect(generate(method)).toEqual(expected[method]); + }); + }); + }); }); diff --git a/test/specs/helpers.interpolation.tests.js b/test/specs/helpers.interpolation.tests.js index 22f8254a3f9..644d93dbcfb 100644 --- a/test/specs/helpers.interpolation.tests.js +++ b/test/specs/helpers.interpolation.tests.js @@ -1,35 +1,35 @@ const {_pointInLine, _steppedInterpolation, _bezierInterpolation} = Chart.helpers; describe('helpers.interpolation', function() { - it('Should interpolate a point in line', function() { - expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0)).toEqual({x: 10, y: 10}); - expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0.5)).toEqual({x: 15, y: 15}); - expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 1)).toEqual({x: 20, y: 20}); - }); + it('Should interpolate a point in line', function() { + expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0)).toEqual({x: 10, y: 10}); + expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0.5)).toEqual({x: 15, y: 15}); + expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 1)).toEqual({x: 20, y: 20}); + }); - it('Should intepolate a point in stepped line', function() { - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'before')).toEqual({x: 10, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'before')).toEqual({x: 14, y: 20}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'before')).toEqual({x: 15, y: 20}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'before')).toEqual({x: 20, y: 20}); + it('Should intepolate a point in stepped line', function() { + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'before')).toEqual({x: 10, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'before')).toEqual({x: 14, y: 20}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'before')).toEqual({x: 15, y: 20}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'before')).toEqual({x: 20, y: 20}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'middle')).toEqual({x: 10, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'middle')).toEqual({x: 14, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'middle')).toEqual({x: 15, y: 20}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'middle')).toEqual({x: 20, y: 20}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'middle')).toEqual({x: 10, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'middle')).toEqual({x: 14, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'middle')).toEqual({x: 15, y: 20}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'middle')).toEqual({x: 20, y: 20}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'after')).toEqual({x: 10, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'after')).toEqual({x: 14, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'after')).toEqual({x: 15, y: 10}); - expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'after')).toEqual({x: 20, y: 20}); - }); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'after')).toEqual({x: 10, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'after')).toEqual({x: 14, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'after')).toEqual({x: 15, y: 10}); + expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'after')).toEqual({x: 20, y: 20}); + }); - it('Should interpolate a point in curve', function() { - const pt1 = {x: 10, y: 10, controlPointNextX: 12, controlPointNextY: 12}; - const pt2 = {x: 20, y: 30, controlPointPreviousX: 18, controlPointPreviousY: 28}; + it('Should interpolate a point in curve', function() { + const pt1 = {x: 10, y: 10, controlPointNextX: 12, controlPointNextY: 12}; + const pt2 = {x: 20, y: 30, controlPointPreviousX: 18, controlPointPreviousY: 28}; - expect(_bezierInterpolation(pt1, pt2, 0)).toEqual({x: 10, y: 10}); - expect(_bezierInterpolation(pt1, pt2, 0.2)).toBeCloseToPoint({x: 11.616, y: 12.656}); - expect(_bezierInterpolation(pt1, pt2, 1)).toEqual({x: 20, y: 30}); - }); + expect(_bezierInterpolation(pt1, pt2, 0)).toEqual({x: 10, y: 10}); + expect(_bezierInterpolation(pt1, pt2, 0.2)).toBeCloseToPoint({x: 11.616, y: 12.656}); + expect(_bezierInterpolation(pt1, pt2, 1)).toEqual({x: 20, y: 30}); + }); }); diff --git a/test/specs/helpers.math.tests.js b/test/specs/helpers.math.tests.js index c71902201b1..b6b8e125f09 100644 --- a/test/specs/helpers.math.tests.js +++ b/test/specs/helpers.math.tests.js @@ -1,139 +1,139 @@ const math = Chart.helpers; describe('Chart.helpers.math', function() { - var factorize = math._factorize; - var decimalPlaces = math._decimalPlaces; - - it('should factorize', function() { - expect(factorize(1000)).toEqual([1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, 125, 200, 250, 500]); - expect(factorize(60)).toEqual([1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]); - expect(factorize(30)).toEqual([1, 2, 3, 5, 6, 10, 15]); - expect(factorize(24)).toEqual([1, 2, 3, 4, 6, 8, 12]); - expect(factorize(12)).toEqual([1, 2, 3, 4, 6]); - expect(factorize(4)).toEqual([1, 2]); - expect(factorize(-1)).toEqual([]); - expect(factorize(2.76)).toEqual([]); - }); - - it('should do a log10 operation', function() { - expect(math.log10(0)).toBe(-Infinity); - - // Check all allowed powers of 10, which should return integer values - var maxPowerOf10 = Math.floor(math.log10(Number.MAX_VALUE)); - for (var i = 0; i < maxPowerOf10; i += 1) { - expect(math.log10(Math.pow(10, i))).toBe(i); - } - }); - - it('should get the correct number of decimal places', function() { - expect(decimalPlaces(100)).toBe(0); - expect(decimalPlaces(1)).toBe(0); - expect(decimalPlaces(0)).toBe(0); - expect(decimalPlaces(0.01)).toBe(2); - expect(decimalPlaces(-0.01)).toBe(2); - expect(decimalPlaces('1')).toBe(undefined); - expect(decimalPlaces('')).toBe(undefined); - expect(decimalPlaces(undefined)).toBe(undefined); - expect(decimalPlaces(12345678.1234)).toBe(4); - expect(decimalPlaces(1234567890.1234567)).toBe(7); - }); - - it('should get an angle from a point', function() { - var center = { - x: 0, - y: 0 - }; - - expect(math.getAngleFromPoint(center, { - x: 0, - y: 10 - })).toEqual({ - angle: Math.PI / 2, - distance: 10, - }); - - expect(math.getAngleFromPoint(center, { - x: Math.sqrt(2), - y: Math.sqrt(2) - })).toEqual({ - angle: Math.PI / 4, - distance: 2 - }); - - expect(math.getAngleFromPoint(center, { - x: -1.0 * Math.sqrt(2), - y: -1.0 * Math.sqrt(2) - })).toEqual({ - angle: Math.PI * 1.25, - distance: 2 - }); - }); - - it('should convert between radians and degrees', function() { - expect(math.toRadians(180)).toBe(Math.PI); - expect(math.toRadians(90)).toBe(0.5 * Math.PI); - expect(math.toDegrees(Math.PI)).toBe(180); - expect(math.toDegrees(Math.PI * 3 / 2)).toBe(270); - }); - - it('should correctly determine if two numbers are essentially equal', function() { - expect(math.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true); - expect(math.almostEquals(1, 1.1, 0.0001)).toBe(false); - expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false); - expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true); - }); - - it('should get the correct sign', function() { - expect(math.sign(0)).toBe(0); - expect(math.sign(10)).toBe(1); - expect(math.sign(-5)).toBe(-1); - }); - - it('should correctly determine if a numbers are essentially whole', function() { - expect(math.almostWhole(0.99999, 0.0001)).toBe(true); - expect(math.almostWhole(0.9, 0.0001)).toBe(false); - expect(math.almostWhole(1234567890123, 0.0001)).toBe(true); - expect(math.almostWhole(1234567890123.001, 0.0001)).toBe(false); - }); - - it('should detect a number', function() { - expect(math.isNumber(123)).toBe(true); - expect(math.isNumber('123')).toBe(true); - expect(math.isNumber(null)).toBe(false); - expect(math.isNumber(NaN)).toBe(false); - expect(math.isNumber(undefined)).toBe(false); - expect(math.isNumber('cbc')).toBe(false); - }); - - it('should compute shortest distance between angles', function() { - expect(math._angleDiff(1, 2)).toEqual(-1); - expect(math._angleDiff(2, 1)).toEqual(1); - expect(math._angleDiff(0, 3.15)).toBeCloseTo(3.13, 2); - expect(math._angleDiff(0, 3.13)).toEqual(-3.13); - expect(math._angleDiff(6.2, 0)).toBeCloseTo(-0.08, 2); - expect(math._angleDiff(6.3, 0)).toBeCloseTo(0.02, 2); - expect(math._angleDiff(4 * Math.PI, -4 * Math.PI)).toBeCloseTo(0, 4); - expect(math._angleDiff(4 * Math.PI, -3 * Math.PI)).toBeCloseTo(-3.14, 2); - expect(math._angleDiff(6.28, 3.1)).toBeCloseTo(-3.1, 2); - expect(math._angleDiff(6.28, 3.2)).toBeCloseTo(3.08, 2); - }); - - it('should normalize angles correctly', function() { - expect(math._normalizeAngle(-Math.PI)).toEqual(Math.PI); - expect(math._normalizeAngle(Math.PI)).toEqual(Math.PI); - expect(math._normalizeAngle(2)).toEqual(2); - expect(math._normalizeAngle(5 * Math.PI)).toEqual(Math.PI); - expect(math._normalizeAngle(-50 * Math.PI)).toBeCloseTo(6.28, 2); - }); - - it('should determine if angle is between boundaries', function() { - expect(math._angleBetween(2, 1, 3)).toBeTrue(); - expect(math._angleBetween(2, 3, 1)).toBeFalse(); - expect(math._angleBetween(-3.14, 2, 4)).toBeTrue(); - expect(math._angleBetween(-3.14, 4, 2)).toBeFalse(); - expect(math._angleBetween(0, -1, 1)).toBeTrue(); - expect(math._angleBetween(-1, 0, 1)).toBeFalse(); - expect(math._angleBetween(-15 * Math.PI, 3.1, 3.2)).toBeTrue(); - expect(math._angleBetween(15 * Math.PI, -3.2, -3.1)).toBeTrue(); - }); + var factorize = math._factorize; + var decimalPlaces = math._decimalPlaces; + + it('should factorize', function() { + expect(factorize(1000)).toEqual([1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, 125, 200, 250, 500]); + expect(factorize(60)).toEqual([1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]); + expect(factorize(30)).toEqual([1, 2, 3, 5, 6, 10, 15]); + expect(factorize(24)).toEqual([1, 2, 3, 4, 6, 8, 12]); + expect(factorize(12)).toEqual([1, 2, 3, 4, 6]); + expect(factorize(4)).toEqual([1, 2]); + expect(factorize(-1)).toEqual([]); + expect(factorize(2.76)).toEqual([]); + }); + + it('should do a log10 operation', function() { + expect(math.log10(0)).toBe(-Infinity); + + // Check all allowed powers of 10, which should return integer values + var maxPowerOf10 = Math.floor(math.log10(Number.MAX_VALUE)); + for (var i = 0; i < maxPowerOf10; i += 1) { + expect(math.log10(Math.pow(10, i))).toBe(i); + } + }); + + it('should get the correct number of decimal places', function() { + expect(decimalPlaces(100)).toBe(0); + expect(decimalPlaces(1)).toBe(0); + expect(decimalPlaces(0)).toBe(0); + expect(decimalPlaces(0.01)).toBe(2); + expect(decimalPlaces(-0.01)).toBe(2); + expect(decimalPlaces('1')).toBe(undefined); + expect(decimalPlaces('')).toBe(undefined); + expect(decimalPlaces(undefined)).toBe(undefined); + expect(decimalPlaces(12345678.1234)).toBe(4); + expect(decimalPlaces(1234567890.1234567)).toBe(7); + }); + + it('should get an angle from a point', function() { + var center = { + x: 0, + y: 0 + }; + + expect(math.getAngleFromPoint(center, { + x: 0, + y: 10 + })).toEqual({ + angle: Math.PI / 2, + distance: 10, + }); + + expect(math.getAngleFromPoint(center, { + x: Math.sqrt(2), + y: Math.sqrt(2) + })).toEqual({ + angle: Math.PI / 4, + distance: 2 + }); + + expect(math.getAngleFromPoint(center, { + x: -1.0 * Math.sqrt(2), + y: -1.0 * Math.sqrt(2) + })).toEqual({ + angle: Math.PI * 1.25, + distance: 2 + }); + }); + + it('should convert between radians and degrees', function() { + expect(math.toRadians(180)).toBe(Math.PI); + expect(math.toRadians(90)).toBe(0.5 * Math.PI); + expect(math.toDegrees(Math.PI)).toBe(180); + expect(math.toDegrees(Math.PI * 3 / 2)).toBe(270); + }); + + it('should correctly determine if two numbers are essentially equal', function() { + expect(math.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true); + expect(math.almostEquals(1, 1.1, 0.0001)).toBe(false); + expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false); + expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true); + }); + + it('should get the correct sign', function() { + expect(math.sign(0)).toBe(0); + expect(math.sign(10)).toBe(1); + expect(math.sign(-5)).toBe(-1); + }); + + it('should correctly determine if a numbers are essentially whole', function() { + expect(math.almostWhole(0.99999, 0.0001)).toBe(true); + expect(math.almostWhole(0.9, 0.0001)).toBe(false); + expect(math.almostWhole(1234567890123, 0.0001)).toBe(true); + expect(math.almostWhole(1234567890123.001, 0.0001)).toBe(false); + }); + + it('should detect a number', function() { + expect(math.isNumber(123)).toBe(true); + expect(math.isNumber('123')).toBe(true); + expect(math.isNumber(null)).toBe(false); + expect(math.isNumber(NaN)).toBe(false); + expect(math.isNumber(undefined)).toBe(false); + expect(math.isNumber('cbc')).toBe(false); + }); + + it('should compute shortest distance between angles', function() { + expect(math._angleDiff(1, 2)).toEqual(-1); + expect(math._angleDiff(2, 1)).toEqual(1); + expect(math._angleDiff(0, 3.15)).toBeCloseTo(3.13, 2); + expect(math._angleDiff(0, 3.13)).toEqual(-3.13); + expect(math._angleDiff(6.2, 0)).toBeCloseTo(-0.08, 2); + expect(math._angleDiff(6.3, 0)).toBeCloseTo(0.02, 2); + expect(math._angleDiff(4 * Math.PI, -4 * Math.PI)).toBeCloseTo(0, 4); + expect(math._angleDiff(4 * Math.PI, -3 * Math.PI)).toBeCloseTo(-3.14, 2); + expect(math._angleDiff(6.28, 3.1)).toBeCloseTo(-3.1, 2); + expect(math._angleDiff(6.28, 3.2)).toBeCloseTo(3.08, 2); + }); + + it('should normalize angles correctly', function() { + expect(math._normalizeAngle(-Math.PI)).toEqual(Math.PI); + expect(math._normalizeAngle(Math.PI)).toEqual(Math.PI); + expect(math._normalizeAngle(2)).toEqual(2); + expect(math._normalizeAngle(5 * Math.PI)).toEqual(Math.PI); + expect(math._normalizeAngle(-50 * Math.PI)).toBeCloseTo(6.28, 2); + }); + + it('should determine if angle is between boundaries', function() { + expect(math._angleBetween(2, 1, 3)).toBeTrue(); + expect(math._angleBetween(2, 3, 1)).toBeFalse(); + expect(math._angleBetween(-3.14, 2, 4)).toBeTrue(); + expect(math._angleBetween(-3.14, 4, 2)).toBeFalse(); + expect(math._angleBetween(0, -1, 1)).toBeTrue(); + expect(math._angleBetween(-1, 0, 1)).toBeFalse(); + expect(math._angleBetween(-15 * Math.PI, 3.1, 3.2)).toBeTrue(); + expect(math._angleBetween(15 * Math.PI, -3.2, -3.1)).toBeTrue(); + }); }); diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js index 31beec8fe8e..8bbacba9a0a 100644 --- a/test/specs/helpers.options.tests.js +++ b/test/specs/helpers.options.tests.js @@ -1,248 +1,248 @@ const {toLineHeight, toPadding, toFont, resolve, toTRBLCorners} = Chart.helpers; describe('Chart.helpers.options', function() { - describe('toLineHeight', function() { - it ('should support keyword values', function() { - expect(toLineHeight('normal', 16)).toBe(16 * 1.2); - }); - it ('should support unitless values', function() { - expect(toLineHeight(1.4, 16)).toBe(16 * 1.4); - expect(toLineHeight('1.4', 16)).toBe(16 * 1.4); - }); - it ('should support length values', function() { - expect(toLineHeight('42px', 16)).toBe(42); - expect(toLineHeight('1.4em', 16)).toBe(16 * 1.4); - }); - it ('should support percentage values', function() { - expect(toLineHeight('140%', 16)).toBe(16 * 1.4); - }); - it ('should fallback to default (1.2) for invalid values', function() { - expect(toLineHeight(null, 16)).toBe(16 * 1.2); - expect(toLineHeight(undefined, 16)).toBe(16 * 1.2); - expect(toLineHeight('foobar', 16)).toBe(16 * 1.2); - }); - }); + describe('toLineHeight', function() { + it ('should support keyword values', function() { + expect(toLineHeight('normal', 16)).toBe(16 * 1.2); + }); + it ('should support unitless values', function() { + expect(toLineHeight(1.4, 16)).toBe(16 * 1.4); + expect(toLineHeight('1.4', 16)).toBe(16 * 1.4); + }); + it ('should support length values', function() { + expect(toLineHeight('42px', 16)).toBe(42); + expect(toLineHeight('1.4em', 16)).toBe(16 * 1.4); + }); + it ('should support percentage values', function() { + expect(toLineHeight('140%', 16)).toBe(16 * 1.4); + }); + it ('should fallback to default (1.2) for invalid values', function() { + expect(toLineHeight(null, 16)).toBe(16 * 1.2); + expect(toLineHeight(undefined, 16)).toBe(16 * 1.2); + expect(toLineHeight('foobar', 16)).toBe(16 * 1.2); + }); + }); - describe('toTRBLCorners', function() { - it('should support number values', function() { - expect(toTRBLCorners(4)).toEqual( - {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4}); - expect(toTRBLCorners(4.5)).toEqual( - {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5}); - }); - it('should support string values', function() { - expect(toTRBLCorners('4')).toEqual( - {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4}); - expect(toTRBLCorners('4.5')).toEqual( - {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5}); - }); - it('should support object values', function() { - expect(toTRBLCorners({topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4})).toEqual( - {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4}); - expect(toTRBLCorners({topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5})).toEqual( - {topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5}); - expect(toTRBLCorners({topLeft: '1', topRight: '2', bottomLeft: '3', bottomRight: '4'})).toEqual( - {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4}); - }); - it('should fallback to 0 for invalid values', function() { - expect(toTRBLCorners({topLeft: 'foo', topRight: 'foo', bottomLeft: 'foo', bottomRight: 'foo'})).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - expect(toTRBLCorners({topLeft: null, topRight: null, bottomLeft: null, bottomRight: null})).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - expect(toTRBLCorners({})).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - expect(toTRBLCorners('foo')).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - expect(toTRBLCorners(null)).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - expect(toTRBLCorners(undefined)).toEqual( - {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); - }); - }); + describe('toTRBLCorners', function() { + it('should support number values', function() { + expect(toTRBLCorners(4)).toEqual( + {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4}); + expect(toTRBLCorners(4.5)).toEqual( + {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5}); + }); + it('should support string values', function() { + expect(toTRBLCorners('4')).toEqual( + {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4}); + expect(toTRBLCorners('4.5')).toEqual( + {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5}); + }); + it('should support object values', function() { + expect(toTRBLCorners({topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4})).toEqual( + {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4}); + expect(toTRBLCorners({topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5})).toEqual( + {topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5}); + expect(toTRBLCorners({topLeft: '1', topRight: '2', bottomLeft: '3', bottomRight: '4'})).toEqual( + {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4}); + }); + it('should fallback to 0 for invalid values', function() { + expect(toTRBLCorners({topLeft: 'foo', topRight: 'foo', bottomLeft: 'foo', bottomRight: 'foo'})).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + expect(toTRBLCorners({topLeft: null, topRight: null, bottomLeft: null, bottomRight: null})).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + expect(toTRBLCorners({})).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + expect(toTRBLCorners('foo')).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + expect(toTRBLCorners(null)).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + expect(toTRBLCorners(undefined)).toEqual( + {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0}); + }); + }); - describe('toPadding', function() { - it ('should support number values', function() { - expect(toPadding(4)).toEqual( - {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); - expect(toPadding(4.5)).toEqual( - {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); - }); - it ('should support string values', function() { - expect(toPadding('4')).toEqual( - {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); - expect(toPadding('4.5')).toEqual( - {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); - }); - it ('should support object values', function() { - expect(toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual( - {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); - expect(toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual( - {top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7}); - expect(toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual( - {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); - }); - it ('should fallback to 0 for invalid values', function() { - expect(toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(toPadding({top: null, right: null, bottom: null, left: null})).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(toPadding({})).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(toPadding('foo')).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(toPadding(null)).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(toPadding(undefined)).toEqual( - {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - }); - }); + describe('toPadding', function() { + it ('should support number values', function() { + expect(toPadding(4)).toEqual( + {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); + expect(toPadding(4.5)).toEqual( + {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); + }); + it ('should support string values', function() { + expect(toPadding('4')).toEqual( + {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); + expect(toPadding('4.5')).toEqual( + {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); + }); + it ('should support object values', function() { + expect(toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual( + {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); + expect(toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual( + {top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7}); + expect(toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual( + {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); + }); + it ('should fallback to 0 for invalid values', function() { + expect(toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(toPadding({top: null, right: null, bottom: null, left: null})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(toPadding({})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(toPadding('foo')).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(toPadding(null)).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(toPadding(undefined)).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + }); + }); - describe('toFont', function() { - it('should return a font with default values', function() { - const defaultFont = Object.assign({}, Chart.defaults.font); + describe('toFont', function() { + it('should return a font with default values', function() { + const defaultFont = Object.assign({}, Chart.defaults.font); - Object.assign(Chart.defaults.font, { - family: 'foobar', - size: 42, - style: 'xxxyyy', - lineHeight: 1.5 - }); + Object.assign(Chart.defaults.font, { + family: 'foobar', + size: 42, + style: 'xxxyyy', + lineHeight: 1.5 + }); - expect(toFont({})).toEqual({ - family: 'foobar', - lineHeight: 63, - size: 42, - string: 'xxxyyy 42px foobar', - style: 'xxxyyy', - weight: null - }); + expect(toFont({})).toEqual({ + family: 'foobar', + lineHeight: 63, + size: 42, + string: 'xxxyyy 42px foobar', + style: 'xxxyyy', + weight: null + }); - Object.assign(Chart.defaults.font, defaultFont); - }); - it ('should return a font with given values', function() { - expect(toFont({ - family: 'bla', - lineHeight: 8, - size: 21, - style: 'zzz' - })).toEqual({ - family: 'bla', - lineHeight: 8 * 21, - size: 21, - string: 'zzz 21px bla', - style: 'zzz', - weight: null - }); - }); - it ('should handle a string font size', function() { - expect(toFont({ - family: 'bla', - lineHeight: 8, - size: '21', - style: 'zzz' - })).toEqual({ - family: 'bla', - lineHeight: 8 * 21, - size: 21, - string: 'zzz 21px bla', - style: 'zzz', - weight: null - }); - }); - it('should return null as a font string if size or family are missing', function() { - const fontFamily = Chart.defaults.font.family; - const fontSize = Chart.defaults.font.size; - delete Chart.defaults.font.family; - delete Chart.defaults.font.size; + Object.assign(Chart.defaults.font, defaultFont); + }); + it ('should return a font with given values', function() { + expect(toFont({ + family: 'bla', + lineHeight: 8, + size: 21, + style: 'zzz' + })).toEqual({ + family: 'bla', + lineHeight: 8 * 21, + size: 21, + string: 'zzz 21px bla', + style: 'zzz', + weight: null + }); + }); + it ('should handle a string font size', function() { + expect(toFont({ + family: 'bla', + lineHeight: 8, + size: '21', + style: 'zzz' + })).toEqual({ + family: 'bla', + lineHeight: 8 * 21, + size: 21, + string: 'zzz 21px bla', + style: 'zzz', + weight: null + }); + }); + it('should return null as a font string if size or family are missing', function() { + const fontFamily = Chart.defaults.font.family; + const fontSize = Chart.defaults.font.size; + delete Chart.defaults.font.family; + delete Chart.defaults.font.size; - expect(toFont({ - style: 'italic', - size: 12 - }).string).toBeNull(); - expect(toFont({ - style: 'italic', - family: 'serif' - }).string).toBeNull(); + expect(toFont({ + style: 'italic', + size: 12 + }).string).toBeNull(); + expect(toFont({ + style: 'italic', + family: 'serif' + }).string).toBeNull(); - Chart.defaults.font.family = fontFamily; - Chart.defaults.font.size = fontSize; - }); - it('font.style should be optional for font strings', function() { - const fontStyle = Chart.defaults.font.style; - delete Chart.defaults.font.style; + Chart.defaults.font.family = fontFamily; + Chart.defaults.font.size = fontSize; + }); + it('font.style should be optional for font strings', function() { + const fontStyle = Chart.defaults.font.style; + delete Chart.defaults.font.style; - expect(toFont({ - size: 12, - family: 'serif' - }).string).toBe('12px serif'); + expect(toFont({ + size: 12, + family: 'serif' + }).string).toBe('12px serif'); - Chart.defaults.font.style = fontStyle; - }); - }); + Chart.defaults.font.style = fontStyle; + }); + }); - describe('resolve', function() { - it ('should fallback to the first defined input', function() { - expect(resolve([42])).toBe(42); - expect(resolve([42, 'foo'])).toBe(42); - expect(resolve([undefined, 42, 'foo'])).toBe(42); - expect(resolve([42, 'foo', undefined])).toBe(42); - expect(resolve([undefined])).toBe(undefined); - }); - it ('should correctly handle empty values (null, 0, "")', function() { - expect(resolve([0, 'foo'])).toBe(0); - expect(resolve(['', 'foo'])).toBe(''); - expect(resolve([null, 'foo'])).toBe(null); - }); - it ('should support indexable options if index is provided', function() { - var input = [42, 'foo', 'bar']; - expect(resolve([input], undefined, 0)).toBe(42); - expect(resolve([input], undefined, 1)).toBe('foo'); - expect(resolve([input], undefined, 2)).toBe('bar'); - }); - it ('should fallback if an indexable option value is undefined', function() { - var input = [42, undefined, 'bar']; - expect(resolve([input], undefined, 1)).toBe(undefined); - expect(resolve([input, 'foo'], undefined, 1)).toBe('foo'); - }); - it ('should loop if an indexable option index is out of bounds', function() { - var input = [42, undefined, 'bar']; - expect(resolve([input], undefined, 3)).toBe(42); - expect(resolve([input, 'foo'], undefined, 4)).toBe('foo'); - expect(resolve([input, 'foo'], undefined, 5)).toBe('bar'); - }); - it ('should not handle indexable options if index is undefined', function() { - var array = [42, 'foo', 'bar']; - expect(resolve([array])).toBe(array); - expect(resolve([array], undefined, undefined)).toBe(array); - }); - it ('should support scriptable options if context is provided', function() { - var input = function(context) { - return context.v * 2; - }; - expect(resolve([42], {v: 42})).toBe(42); - expect(resolve([input], {v: 42})).toBe(84); - }); - it ('should fallback if a scriptable option returns undefined', function() { - var input = function() {}; - expect(resolve([input], {v: 42})).toBe(undefined); - expect(resolve([input, 'foo'], {v: 42})).toBe('foo'); - expect(resolve([input, undefined, 'foo'], {v: 42})).toBe('foo'); - }); - it ('should not handle scriptable options if context is undefined', function() { - var input = function(context) { - return context.v * 2; - }; - expect(resolve([input])).toBe(input); - expect(resolve([input], undefined)).toBe(input); - }); - it ('should handle scriptable and indexable option', function() { - var input = function(context) { - return [context.v, undefined, 'bar']; - }; - expect(resolve([input, 'foo'], {v: 42}, 0)).toBe(42); - expect(resolve([input, 'foo'], {v: 42}, 1)).toBe('foo'); - expect(resolve([input, 'foo'], {v: 42}, 5)).toBe('bar'); - expect(resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar'); - }); - }); + describe('resolve', function() { + it ('should fallback to the first defined input', function() { + expect(resolve([42])).toBe(42); + expect(resolve([42, 'foo'])).toBe(42); + expect(resolve([undefined, 42, 'foo'])).toBe(42); + expect(resolve([42, 'foo', undefined])).toBe(42); + expect(resolve([undefined])).toBe(undefined); + }); + it ('should correctly handle empty values (null, 0, "")', function() { + expect(resolve([0, 'foo'])).toBe(0); + expect(resolve(['', 'foo'])).toBe(''); + expect(resolve([null, 'foo'])).toBe(null); + }); + it ('should support indexable options if index is provided', function() { + var input = [42, 'foo', 'bar']; + expect(resolve([input], undefined, 0)).toBe(42); + expect(resolve([input], undefined, 1)).toBe('foo'); + expect(resolve([input], undefined, 2)).toBe('bar'); + }); + it ('should fallback if an indexable option value is undefined', function() { + var input = [42, undefined, 'bar']; + expect(resolve([input], undefined, 1)).toBe(undefined); + expect(resolve([input, 'foo'], undefined, 1)).toBe('foo'); + }); + it ('should loop if an indexable option index is out of bounds', function() { + var input = [42, undefined, 'bar']; + expect(resolve([input], undefined, 3)).toBe(42); + expect(resolve([input, 'foo'], undefined, 4)).toBe('foo'); + expect(resolve([input, 'foo'], undefined, 5)).toBe('bar'); + }); + it ('should not handle indexable options if index is undefined', function() { + var array = [42, 'foo', 'bar']; + expect(resolve([array])).toBe(array); + expect(resolve([array], undefined, undefined)).toBe(array); + }); + it ('should support scriptable options if context is provided', function() { + var input = function(context) { + return context.v * 2; + }; + expect(resolve([42], {v: 42})).toBe(42); + expect(resolve([input], {v: 42})).toBe(84); + }); + it ('should fallback if a scriptable option returns undefined', function() { + var input = function() {}; + expect(resolve([input], {v: 42})).toBe(undefined); + expect(resolve([input, 'foo'], {v: 42})).toBe('foo'); + expect(resolve([input, undefined, 'foo'], {v: 42})).toBe('foo'); + }); + it ('should not handle scriptable options if context is undefined', function() { + var input = function(context) { + return context.v * 2; + }; + expect(resolve([input])).toBe(input); + expect(resolve([input], undefined)).toBe(input); + }); + it ('should handle scriptable and indexable option', function() { + var input = function(context) { + return [context.v, undefined, 'bar']; + }; + expect(resolve([input, 'foo'], {v: 42}, 0)).toBe(42); + expect(resolve([input, 'foo'], {v: 42}, 1)).toBe('foo'); + expect(resolve([input, 'foo'], {v: 42}, 5)).toBe('bar'); + expect(resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar'); + }); + }); }); diff --git a/test/specs/helpers.segment.tests.js b/test/specs/helpers.segment.tests.js index d1578733a7f..c0ac6302603 100644 --- a/test/specs/helpers.segment.tests.js +++ b/test/specs/helpers.segment.tests.js @@ -1,44 +1,44 @@ const {_boundSegment} = Chart.helpers; describe('helpers.segments', function() { - describe('_boundSegment', function() { - const points = [{x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}]; - const segment = {start: 0, end: 2, loop: false}; - - it('should not find segment from before the line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 9.99999})).toEqual([]); - }); - - it('should not find segment from after the line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 30.00001, end: 800})).toEqual([]); - }); - - it('should find segment when starting before line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false}]); - }); - - it('should find segment directly on point', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false}]); - }); - - it('should find segment from range between points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false}]); - }); - - it('should find segment from point between points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false}]); - }); - - it('should find whole segment', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]); - }); - - it('should find correct segment from near points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false}]); - }); - - it('should find segment from after the line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false}]); - }); - }); + describe('_boundSegment', function() { + const points = [{x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}]; + const segment = {start: 0, end: 2, loop: false}; + + it('should not find segment from before the line', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 9.99999})).toEqual([]); + }); + + it('should not find segment from after the line', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 30.00001, end: 800})).toEqual([]); + }); + + it('should find segment when starting before line', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false}]); + }); + + it('should find segment directly on point', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false}]); + }); + + it('should find segment from range between points', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false}]); + }); + + it('should find segment from point between points', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false}]); + }); + + it('should find whole segment', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]); + }); + + it('should find correct segment from near points', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false}]); + }); + + it('should find segment from after the line', function() { + expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false}]); + }); + }); }); diff --git a/test/specs/mixed.tests.js b/test/specs/mixed.tests.js index 236fe95c1f5..0d501e55828 100644 --- a/test/specs/mixed.tests.js +++ b/test/specs/mixed.tests.js @@ -1,3 +1,3 @@ describe('Mixed charts', function() { - describe('auto', jasmine.fixture.specs('mixed')); + describe('auto', jasmine.fixture.specs('mixed')); }); diff --git a/test/specs/platform.basic.tests.js b/test/specs/platform.basic.tests.js index 90a6541b5d8..1ee7278b96b 100644 --- a/test/specs/platform.basic.tests.js +++ b/test/specs/platform.basic.tests.js @@ -1,91 +1,91 @@ describe('Platform.basic', function() { - it('should automatically choose the BasicPlatform for offscreen canvas', function() { - const chart = acquireChart({type: 'line'}, {useOffscreenCanvas: true}); + it('should automatically choose the BasicPlatform for offscreen canvas', function() { + const chart = acquireChart({type: 'line'}, {useOffscreenCanvas: true}); - expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); + expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); - chart.destroy(); - }); + chart.destroy(); + }); - it('supports choosing the BasicPlatform in a web worker', function(done) { - const canvas = document.createElement('canvas'); - if (!canvas.transferControlToOffscreen) { - pending(); - } - const offscreenCanvas = canvas.transferControlToOffscreen(); + it('supports choosing the BasicPlatform in a web worker', function(done) { + const canvas = document.createElement('canvas'); + if (!canvas.transferControlToOffscreen) { + pending(); + } + const offscreenCanvas = canvas.transferControlToOffscreen(); - const worker = new Worker('base/test/BasicChartWebWorker.js'); - worker.onmessage = (event) => { - worker.terminate(); - const {type, errorMessage} = event.data; - if (type === 'error') { - done.fail(errorMessage); - } else if (type === 'success') { - expect(type).toEqual('success'); - done(); - } else { - done.fail('invalid message type sent by worker: ' + type); - } - }; + const worker = new Worker('base/test/BasicChartWebWorker.js'); + worker.onmessage = (event) => { + worker.terminate(); + const {type, errorMessage} = event.data; + if (type === 'error') { + done.fail(errorMessage); + } else if (type === 'success') { + expect(type).toEqual('success'); + done(); + } else { + done.fail('invalid message type sent by worker: ' + type); + } + }; - worker.postMessage({type: 'initialize', canvas: offscreenCanvas}, [offscreenCanvas]); - }); + worker.postMessage({type: 'initialize', canvas: offscreenCanvas}, [offscreenCanvas]); + }); - describe('with offscreenCanvas', function() { - it('supports laying out a simple chart', function() { - const chart = acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - } - }, { - canvas: { - height: 150, - width: 250 - }, - useOffscreenCanvas: true, - }); + describe('with offscreenCanvas', function() { + it('supports laying out a simple chart', function() { + const chart = acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + } + }, { + canvas: { + height: 150, + width: 250 + }, + useOffscreenCanvas: true, + }); - expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); + expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); - expect(chart.chartArea.bottom).toBeCloseToPixel(120); - expect(chart.chartArea.left).toBeCloseToPixel(34); - expect(chart.chartArea.right).toBeCloseToPixel(247); - expect(chart.chartArea.top).toBeCloseToPixel(32); - }); + expect(chart.chartArea.bottom).toBeCloseToPixel(120); + expect(chart.chartArea.left).toBeCloseToPixel(34); + expect(chart.chartArea.right).toBeCloseToPixel(247); + expect(chart.chartArea.top).toBeCloseToPixel(32); + }); - it('supports resizing a chart', function() { - const chart = acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [10, 5, 0, 25, 78, -10]} - ], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] - } - }, { - canvas: { - height: 150, - width: 250 - }, - useOffscreenCanvas: true, - }); + it('supports resizing a chart', function() { + const chart = acquireChart({ + type: 'bar', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6'] + } + }, { + canvas: { + height: 150, + width: 250 + }, + useOffscreenCanvas: true, + }); - expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); + expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform); - const canvasElement = chart.canvas; - canvasElement.height = 200; - canvasElement.width = 300; - chart.resize(); + const canvasElement = chart.canvas; + canvasElement.height = 200; + canvasElement.width = 300; + chart.resize(); - expect(chart.chartArea.bottom).toBeCloseToPixel(150); - expect(chart.chartArea.left).toBeCloseToPixel(34); - expect(chart.chartArea.right).toBeCloseToPixel(297); - expect(chart.chartArea.top).toBeCloseToPixel(32); - }); - }); + expect(chart.chartArea.bottom).toBeCloseToPixel(150); + expect(chart.chartArea.left).toBeCloseToPixel(34); + expect(chart.chartArea.right).toBeCloseToPixel(297); + expect(chart.chartArea.top).toBeCloseToPixel(32); + }); + }); }); diff --git a/test/specs/platform.dom.tests.js b/test/specs/platform.dom.tests.js index e32100249c0..fe65095236d 100644 --- a/test/specs/platform.dom.tests.js +++ b/test/specs/platform.dom.tests.js @@ -2,429 +2,429 @@ const DomPlatform = Chart.platforms.DomPlatform; describe('Platform.dom', function() { - describe('context acquisition', function() { - var canvasId = 'chartjs-canvas'; + describe('context acquisition', function() { + var canvasId = 'chartjs-canvas'; - beforeEach(function() { - var canvas = document.createElement('canvas'); - canvas.setAttribute('id', canvasId); - window.document.body.appendChild(canvas); - }); - - afterEach(function() { - document.getElementById(canvasId).remove(); - }); - - it('should use the DomPlatform by default', function() { - var chart = acquireChart({type: 'line'}); - - expect(chart.platform).toBeInstanceOf(Chart.platforms.DomPlatform); - - chart.destroy(); - }); - - // see https://github.com/chartjs/Chart.js/issues/2807 - it('should gracefully handle invalid item', function() { - var chart = new Chart('foobar'); - - expect(chart).not.toBeValidChart(); - - chart.destroy(); - }); - - it('should accept a DOM element id', function() { - var canvas = document.getElementById(canvasId); - var chart = new Chart(canvasId); - - expect(chart).toBeValidChart(); - expect(chart.canvas).toBe(canvas); - expect(chart.ctx).toBe(canvas.getContext('2d')); - - chart.destroy(); - }); - - it('should accept a canvas element', function() { - var canvas = document.getElementById(canvasId); - var chart = new Chart(canvas); - - expect(chart).toBeValidChart(); - expect(chart.canvas).toBe(canvas); - expect(chart.ctx).toBe(canvas.getContext('2d')); - - chart.destroy(); - }); - - it('should accept a canvas context2D', function() { - var canvas = document.getElementById(canvasId); - var context = canvas.getContext('2d'); - var chart = new Chart(context); - - expect(chart).toBeValidChart(); - expect(chart.canvas).toBe(canvas); - expect(chart.ctx).toBe(context); - - chart.destroy(); - }); - - it('should accept an array containing canvas', function() { - var canvas = document.getElementById(canvasId); - var chart = new Chart([canvas]); - - expect(chart).toBeValidChart(); - expect(chart.canvas).toBe(canvas); - expect(chart.ctx).toBe(canvas.getContext('2d')); - - chart.destroy(); - }); - - it('should accept a canvas from an iframe', function(done) { - var iframe = document.createElement('iframe'); - iframe.onload = function() { - var doc = iframe.contentDocument; - doc.body.innerHTML += ''; - var canvas = doc.getElementById('chart'); - var chart = new Chart(canvas); - - expect(chart).toBeValidChart(); - expect(chart.canvas).toBe(canvas); - expect(chart.ctx).toBe(canvas.getContext('2d')); - - chart.destroy(); - canvas.remove(); - iframe.remove(); - - done(); - }; - - document.body.appendChild(iframe); - }); - }); - - describe('config.options.aspectRatio', function() { - it('should use default "global" aspect ratio for render and display sizes', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 620px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 620, dh: 310, - rw: 620, rh: 310, - }); - }); - - it('should use default "chart" aspect ratio for render and display sizes', function() { - var ratio = Chart.defaults.controllers.doughnut.aspectRatio; - Chart.defaults.controllers.doughnut.aspectRatio = 1; - - var chart = acquireChart({ - type: 'doughnut', - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 425px' - } - }); - - Chart.defaults.controllers.doughnut.aspectRatio = ratio; - - expect(chart).toBeChartOfSize({ - dw: 425, dh: 425, - rw: 425, rh: 425, - }); - }); - - it('should use "user" aspect ratio for render and display sizes', function() { - var chart = acquireChart({ - options: { - responsive: false, - aspectRatio: 3 - } - }, { - canvas: { - style: 'width: 405px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 405, dh: 135, - rw: 405, rh: 135, - }); - }); - - it('should not apply aspect ratio when height specified', function() { - var chart = acquireChart({ - options: { - responsive: false, - aspectRatio: 3 - } - }, { - canvas: { - style: 'width: 400px; height: 410px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 400, dh: 410, - rw: 400, rh: 410, - }); - }); - }); - - describe('config.options.responsive: false', function() { - it('should use default canvas size for render and display sizes', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: '' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 150, - rw: 300, rh: 150, - }); - }); - - it('should use canvas attributes for render and display sizes', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: '', - width: 305, - height: 245, - } - }); - - expect(chart).toBeChartOfSize({ - dw: 305, dh: 245, - rw: 305, rh: 245, - }); - }); - - it('should use canvas style for render and display sizes (if no attributes)', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 345px; height: 125px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 345, dh: 125, - rw: 345, rh: 125, - }); - }); - - it('should use attributes for the render size and style for the display size', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 345px; height: 125px;', - width: 165, - height: 85, - } - }); - - expect(chart).toBeChartOfSize({ - dw: 345, dh: 125, - rw: 165, rh: 85, - }); - }); - - // https://github.com/chartjs/Chart.js/issues/3860 - it('should support decimal display width and/or height', function() { - var chart = acquireChart({ - options: { - responsive: false - } - }, { - canvas: { - style: 'width: 345.42px; height: 125.42px;' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 345, dh: 125, - rw: 345, rh: 125, - }); - }); - }); - - describe('config.options.responsive: true (maintainAspectRatio: true)', function() { - it('should fill parent width and use aspect ratio to calculate height', function() { - var chart = acquireChart({ - options: { - responsive: true, - maintainAspectRatio: true - } - }, { - canvas: { - style: 'width: 150px; height: 245px' - }, - wrapper: { - style: 'width: 300px; height: 350px' - } - }); - - expect(chart).toBeChartOfSize({ - dw: 300, dh: 490, - rw: 300, rh: 490, - }); - }); - }); - - describe('controller.destroy', function() { - it('should reset context to default values', function() { - var wrapper = document.createElement('div'); - var canvas = document.createElement('canvas'); - wrapper.appendChild(canvas); - window.document.body.appendChild(wrapper); - var chart = new Chart(canvas, {}); - var context = chart.ctx; - - chart.destroy(); - - // https://www.w3.org/TR/2dcontext/#conformance-requirements - Chart.helpers.each({ - fillStyle: '#000000', - font: '10px sans-serif', - lineJoin: 'miter', - lineCap: 'butt', - lineWidth: 1, - miterLimit: 10, - shadowBlur: 0, - shadowColor: 'rgba(0, 0, 0, 0)', - shadowOffsetX: 0, - shadowOffsetY: 0, - strokeStyle: '#000000', - textAlign: 'start', - textBaseline: 'alphabetic' - }, function(value, key) { - expect(context[key]).toBe(value); - }); - - wrapper.parentNode.removeChild(wrapper); - }); - - it('should restore canvas initial values', function(done) { - var wrapper = document.createElement('div'); - var canvas = document.createElement('canvas'); - - canvas.setAttribute('width', 180); - canvas.setAttribute('style', 'width: 512px; height: 480px'); - wrapper.setAttribute('style', 'width: 450px; height: 450px; position: relative'); - - wrapper.appendChild(canvas); - window.document.body.appendChild(wrapper); - - var chart = new Chart(canvas.getContext('2d'), { - options: { - responsive: true, - maintainAspectRatio: false - } - }); - - waitForResize(chart, function() { - expect(chart).toBeChartOfSize({ - dw: 475, dh: 450, - rw: 475, rh: 450, - }); - - chart.destroy(); - - expect(canvas.getAttribute('width')).toBe('180'); - expect(canvas.getAttribute('height')).toBe(null); - expect(canvas.style.width).toBe('512px'); - expect(canvas.style.height).toBe('480px'); - expect(canvas.style.display).toBe(''); - - wrapper.parentNode.removeChild(wrapper); - done(); - }); - wrapper.style.width = '475px'; - }); - }); - - describe('event handling', function() { - it('should notify plugins about events', function(done) { - var notifiedEvent; - var plugin = { - afterEvent: function(chart, args) { - notifiedEvent = args.event; - } - }; - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - responsive: true - }, - plugins: [plugin] - }); - - afterEvent(chart, 'click', function() { - // Check that notifiedEvent is correct - expect(notifiedEvent).not.toBe(undefined); - - // Is type correctly translated - expect(notifiedEvent.type).toBe('click'); - - // Relative Position - expect(notifiedEvent.x).toBeCloseToPixel(chart.width / 2); - expect(notifiedEvent.y).toBeCloseToPixel(chart.height / 2); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'click', { - x: chart.width / 2, - y: chart.height / 2 - }); - }); - }); - - describe('isAttached', function() { - it('should detect detached when canvas is attached to DOM', function() { - var platform = new DomPlatform(); - var canvas = document.createElement('canvas'); - var div = document.createElement('div'); - - expect(platform.isAttached(canvas)).toEqual(false); - div.appendChild(canvas); - expect(platform.isAttached(canvas)).toEqual(false); - document.body.appendChild(div); - - expect(platform.isAttached(canvas)).toEqual(true); - - div.removeChild(canvas); - expect(platform.isAttached(canvas)).toEqual(false); - document.body.removeChild(div); - expect(platform.isAttached(canvas)).toEqual(false); - }); - }); + beforeEach(function() { + var canvas = document.createElement('canvas'); + canvas.setAttribute('id', canvasId); + window.document.body.appendChild(canvas); + }); + + afterEach(function() { + document.getElementById(canvasId).remove(); + }); + + it('should use the DomPlatform by default', function() { + var chart = acquireChart({type: 'line'}); + + expect(chart.platform).toBeInstanceOf(Chart.platforms.DomPlatform); + + chart.destroy(); + }); + + // see https://github.com/chartjs/Chart.js/issues/2807 + it('should gracefully handle invalid item', function() { + var chart = new Chart('foobar'); + + expect(chart).not.toBeValidChart(); + + chart.destroy(); + }); + + it('should accept a DOM element id', function() { + var canvas = document.getElementById(canvasId); + var chart = new Chart(canvasId); + + expect(chart).toBeValidChart(); + expect(chart.canvas).toBe(canvas); + expect(chart.ctx).toBe(canvas.getContext('2d')); + + chart.destroy(); + }); + + it('should accept a canvas element', function() { + var canvas = document.getElementById(canvasId); + var chart = new Chart(canvas); + + expect(chart).toBeValidChart(); + expect(chart.canvas).toBe(canvas); + expect(chart.ctx).toBe(canvas.getContext('2d')); + + chart.destroy(); + }); + + it('should accept a canvas context2D', function() { + var canvas = document.getElementById(canvasId); + var context = canvas.getContext('2d'); + var chart = new Chart(context); + + expect(chart).toBeValidChart(); + expect(chart.canvas).toBe(canvas); + expect(chart.ctx).toBe(context); + + chart.destroy(); + }); + + it('should accept an array containing canvas', function() { + var canvas = document.getElementById(canvasId); + var chart = new Chart([canvas]); + + expect(chart).toBeValidChart(); + expect(chart.canvas).toBe(canvas); + expect(chart.ctx).toBe(canvas.getContext('2d')); + + chart.destroy(); + }); + + it('should accept a canvas from an iframe', function(done) { + var iframe = document.createElement('iframe'); + iframe.onload = function() { + var doc = iframe.contentDocument; + doc.body.innerHTML += ''; + var canvas = doc.getElementById('chart'); + var chart = new Chart(canvas); + + expect(chart).toBeValidChart(); + expect(chart.canvas).toBe(canvas); + expect(chart.ctx).toBe(canvas.getContext('2d')); + + chart.destroy(); + canvas.remove(); + iframe.remove(); + + done(); + }; + + document.body.appendChild(iframe); + }); + }); + + describe('config.options.aspectRatio', function() { + it('should use default "global" aspect ratio for render and display sizes', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 620px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 620, dh: 310, + rw: 620, rh: 310, + }); + }); + + it('should use default "chart" aspect ratio for render and display sizes', function() { + var ratio = Chart.defaults.controllers.doughnut.aspectRatio; + Chart.defaults.controllers.doughnut.aspectRatio = 1; + + var chart = acquireChart({ + type: 'doughnut', + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 425px' + } + }); + + Chart.defaults.controllers.doughnut.aspectRatio = ratio; + + expect(chart).toBeChartOfSize({ + dw: 425, dh: 425, + rw: 425, rh: 425, + }); + }); + + it('should use "user" aspect ratio for render and display sizes', function() { + var chart = acquireChart({ + options: { + responsive: false, + aspectRatio: 3 + } + }, { + canvas: { + style: 'width: 405px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 405, dh: 135, + rw: 405, rh: 135, + }); + }); + + it('should not apply aspect ratio when height specified', function() { + var chart = acquireChart({ + options: { + responsive: false, + aspectRatio: 3 + } + }, { + canvas: { + style: 'width: 400px; height: 410px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 400, dh: 410, + rw: 400, rh: 410, + }); + }); + }); + + describe('config.options.responsive: false', function() { + it('should use default canvas size for render and display sizes', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: '' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 150, + rw: 300, rh: 150, + }); + }); + + it('should use canvas attributes for render and display sizes', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: '', + width: 305, + height: 245, + } + }); + + expect(chart).toBeChartOfSize({ + dw: 305, dh: 245, + rw: 305, rh: 245, + }); + }); + + it('should use canvas style for render and display sizes (if no attributes)', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 345px; height: 125px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 345, dh: 125, + rw: 345, rh: 125, + }); + }); + + it('should use attributes for the render size and style for the display size', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 345px; height: 125px;', + width: 165, + height: 85, + } + }); + + expect(chart).toBeChartOfSize({ + dw: 345, dh: 125, + rw: 165, rh: 85, + }); + }); + + // https://github.com/chartjs/Chart.js/issues/3860 + it('should support decimal display width and/or height', function() { + var chart = acquireChart({ + options: { + responsive: false + } + }, { + canvas: { + style: 'width: 345.42px; height: 125.42px;' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 345, dh: 125, + rw: 345, rh: 125, + }); + }); + }); + + describe('config.options.responsive: true (maintainAspectRatio: true)', function() { + it('should fill parent width and use aspect ratio to calculate height', function() { + var chart = acquireChart({ + options: { + responsive: true, + maintainAspectRatio: true + } + }, { + canvas: { + style: 'width: 150px; height: 245px' + }, + wrapper: { + style: 'width: 300px; height: 350px' + } + }); + + expect(chart).toBeChartOfSize({ + dw: 300, dh: 490, + rw: 300, rh: 490, + }); + }); + }); + + describe('controller.destroy', function() { + it('should reset context to default values', function() { + var wrapper = document.createElement('div'); + var canvas = document.createElement('canvas'); + wrapper.appendChild(canvas); + window.document.body.appendChild(wrapper); + var chart = new Chart(canvas, {}); + var context = chart.ctx; + + chart.destroy(); + + // https://www.w3.org/TR/2dcontext/#conformance-requirements + Chart.helpers.each({ + fillStyle: '#000000', + font: '10px sans-serif', + lineJoin: 'miter', + lineCap: 'butt', + lineWidth: 1, + miterLimit: 10, + shadowBlur: 0, + shadowColor: 'rgba(0, 0, 0, 0)', + shadowOffsetX: 0, + shadowOffsetY: 0, + strokeStyle: '#000000', + textAlign: 'start', + textBaseline: 'alphabetic' + }, function(value, key) { + expect(context[key]).toBe(value); + }); + + wrapper.parentNode.removeChild(wrapper); + }); + + it('should restore canvas initial values', function(done) { + var wrapper = document.createElement('div'); + var canvas = document.createElement('canvas'); + + canvas.setAttribute('width', 180); + canvas.setAttribute('style', 'width: 512px; height: 480px'); + wrapper.setAttribute('style', 'width: 450px; height: 450px; position: relative'); + + wrapper.appendChild(canvas); + window.document.body.appendChild(wrapper); + + var chart = new Chart(canvas.getContext('2d'), { + options: { + responsive: true, + maintainAspectRatio: false + } + }); + + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 475, dh: 450, + rw: 475, rh: 450, + }); + + chart.destroy(); + + expect(canvas.getAttribute('width')).toBe('180'); + expect(canvas.getAttribute('height')).toBe(null); + expect(canvas.style.width).toBe('512px'); + expect(canvas.style.height).toBe('480px'); + expect(canvas.style.display).toBe(''); + + wrapper.parentNode.removeChild(wrapper); + done(); + }); + wrapper.style.width = '475px'; + }); + }); + + describe('event handling', function() { + it('should notify plugins about events', function(done) { + var notifiedEvent; + var plugin = { + afterEvent: function(chart, args) { + notifiedEvent = args.event; + } + }; + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + }, + plugins: [plugin] + }); + + afterEvent(chart, 'click', function() { + // Check that notifiedEvent is correct + expect(notifiedEvent).not.toBe(undefined); + + // Is type correctly translated + expect(notifiedEvent.type).toBe('click'); + + // Relative Position + expect(notifiedEvent.x).toBeCloseToPixel(chart.width / 2); + expect(notifiedEvent.y).toBeCloseToPixel(chart.height / 2); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'click', { + x: chart.width / 2, + y: chart.height / 2 + }); + }); + }); + + describe('isAttached', function() { + it('should detect detached when canvas is attached to DOM', function() { + var platform = new DomPlatform(); + var canvas = document.createElement('canvas'); + var div = document.createElement('div'); + + expect(platform.isAttached(canvas)).toEqual(false); + div.appendChild(canvas); + expect(platform.isAttached(canvas)).toEqual(false); + document.body.appendChild(div); + + expect(platform.isAttached(canvas)).toEqual(true); + + div.removeChild(canvas); + expect(platform.isAttached(canvas)).toEqual(false); + document.body.removeChild(div); + expect(platform.isAttached(canvas)).toEqual(false); + }); + }); }); diff --git a/test/specs/plugin.filler.tests.js b/test/specs/plugin.filler.tests.js index 9b9eb214d2b..067781ebe4c 100644 --- a/test/specs/plugin.filler.tests.js +++ b/test/specs/plugin.filler.tests.js @@ -1,261 +1,261 @@ describe('Plugin.filler', function() { - function decodedFillValues(chart) { - return chart.data.datasets.map(function(dataset, index) { - var meta = chart.getDatasetMeta(index) || {}; - expect(meta.$filler).toBeDefined(); - return meta.$filler.fill; - }); - } + function decodedFillValues(chart) { + return chart.data.datasets.map(function(dataset, index) { + var meta = chart.getDatasetMeta(index) || {}; + expect(meta.$filler).toBeDefined(); + return meta.$filler.fill; + }); + } - describe('auto', jasmine.fixture.specs('plugin.filler')); + describe('auto', jasmine.fixture.specs('plugin.filler')); - describe('dataset.fill', function() { - it('should support boundaries', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 'origin'}, - {fill: 'start'}, - {fill: 'end'}, - ] - } - }); + describe('dataset.fill', function() { + it('should support boundaries', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 'origin'}, + {fill: 'start'}, + {fill: 'end'}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual(['origin', 'start', 'end']); - }); + expect(decodedFillValues(chart)).toEqual(['origin', 'start', 'end']); + }); - it('should support absolute dataset index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 1}, - {fill: 3}, - {fill: 0}, - {fill: 2}, - ] - } - }); + it('should support absolute dataset index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 1}, + {fill: 3}, + {fill: 0}, + {fill: 2}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual([1, 3, 0, 2]); - }); + expect(decodedFillValues(chart)).toEqual([1, 3, 0, 2]); + }); - it('should support relative dataset index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: '+3'}, - {fill: '-1'}, - {fill: '+1'}, - {fill: '-2'}, - ] - } - }); + it('should support relative dataset index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: '+3'}, + {fill: '-1'}, + {fill: '+1'}, + {fill: '-2'}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual([ - 3, // 0 + 3 - 0, // 1 - 1 - 3, // 2 + 1 - 1, // 3 - 2 - ]); - }); + expect(decodedFillValues(chart)).toEqual([ + 3, // 0 + 3 + 0, // 1 - 1 + 3, // 2 + 1 + 1, // 3 - 2 + ]); + }); - it('should handle default fill when true (origin)', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: true}, - {fill: false}, - ] - } - }); + it('should handle default fill when true (origin)', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: true}, + {fill: false}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual(['origin', false]); - }); + expect(decodedFillValues(chart)).toEqual(['origin', false]); + }); - it('should ignore self dataset index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 0}, - {fill: '-0'}, - {fill: '+0'}, - {fill: 3}, - ] - } - }); + it('should ignore self dataset index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 0}, + {fill: '-0'}, + {fill: '+0'}, + {fill: 3}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual([ - false, // 0 === 0 - false, // 1 === 1 - 0 - false, // 2 === 2 + 0 - false, // 3 === 3 - ]); - }); + expect(decodedFillValues(chart)).toEqual([ + false, // 0 === 0 + false, // 1 === 1 - 0 + false, // 2 === 2 + 0 + false, // 3 === 3 + ]); + }); - it('should ignore out of bounds dataset index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: -2}, - {fill: 4}, - {fill: '-3'}, - {fill: '+1'}, - ] - } - }); + it('should ignore out of bounds dataset index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: -2}, + {fill: 4}, + {fill: '-3'}, + {fill: '+1'}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual([ - false, // 0 - 2 < 0 - false, // 1 + 4 > 3 - false, // 2 - 3 < 0 - false, // 3 + 1 > 3 - ]); - }); + expect(decodedFillValues(chart)).toEqual([ + false, // 0 - 2 < 0 + false, // 1 + 4 > 3 + false, // 2 - 3 < 0 + false, // 3 + 1 > 3 + ]); + }); - it('should ignore invalid values', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 'foo'}, - {fill: '+foo'}, - {fill: '-foo'}, - {fill: '+1.1'}, - {fill: '-2.2'}, - {fill: 3.3}, - {fill: -4.4}, - {fill: NaN}, - {fill: Infinity}, - {fill: ''}, - {fill: null}, - {fill: []}, - ] - } - }); + it('should ignore invalid values', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 'foo'}, + {fill: '+foo'}, + {fill: '-foo'}, + {fill: '+1.1'}, + {fill: '-2.2'}, + {fill: 3.3}, + {fill: -4.4}, + {fill: NaN}, + {fill: Infinity}, + {fill: ''}, + {fill: null}, + {fill: []}, + ] + } + }); - expect(decodedFillValues(chart)).toEqual([ - false, // NaN (string) - false, // NaN (string) - false, // NaN (string) - false, // float (string) - false, // float (string) - false, // float (number) - false, // float (number) - false, // NaN - false, // !isFinite - false, // empty string - false, // null - false, // array - ]); - }); - }); + expect(decodedFillValues(chart)).toEqual([ + false, // NaN (string) + false, // NaN (string) + false, // NaN (string) + false, // float (string) + false, // float (string) + false, // float (number) + false, // float (number) + false, // NaN + false, // !isFinite + false, // empty string + false, // null + false, // array + ]); + }); + }); - describe('options.plugins.filler.propagate', function() { - it('should compute propagated fill targets if true', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 'start', hidden: true}, - {fill: '-1', hidden: true}, - {fill: 1, hidden: true}, - {fill: '-2', hidden: true}, - {fill: '+1'}, - {fill: '+2'}, - {fill: '-1'}, - {fill: 'end', hidden: true}, - ] - }, - options: { - plugins: { - filler: { - propagate: true - } - } - } - }); + describe('options.plugins.filler.propagate', function() { + it('should compute propagated fill targets if true', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 'start', hidden: true}, + {fill: '-1', hidden: true}, + {fill: 1, hidden: true}, + {fill: '-2', hidden: true}, + {fill: '+1'}, + {fill: '+2'}, + {fill: '-1'}, + {fill: 'end', hidden: true}, + ] + }, + options: { + plugins: { + filler: { + propagate: true + } + } + } + }); - expect(decodedFillValues(chart)).toEqual([ - 'start', // 'start' - 'start', // 1 - 1 -> 0 (hidden) -> 'start' - 'start', // 1 (hidden) -> 0 (hidden) -> 'start' - 'start', // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 'start' - 5, // 4 + 1 - 'end', // 5 + 2 -> 7 (hidden) -> 'end' - 5, // 6 - 1 -> 5 - 'end', // 'end' - ]); - }); + expect(decodedFillValues(chart)).toEqual([ + 'start', // 'start' + 'start', // 1 - 1 -> 0 (hidden) -> 'start' + 'start', // 1 (hidden) -> 0 (hidden) -> 'start' + 'start', // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 'start' + 5, // 4 + 1 + 'end', // 5 + 2 -> 7 (hidden) -> 'end' + 5, // 6 - 1 -> 5 + 'end', // 'end' + ]); + }); - it('should preserve initial fill targets if false', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: 'start', hidden: true}, - {fill: '-1', hidden: true}, - {fill: 1, hidden: true}, - {fill: '-2', hidden: true}, - {fill: '+1'}, - {fill: '+2'}, - {fill: '-1'}, - {fill: 'end', hidden: true}, - ] - }, - options: { - plugins: { - filler: { - propagate: false - } - } - } - }); + it('should preserve initial fill targets if false', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: 'start', hidden: true}, + {fill: '-1', hidden: true}, + {fill: 1, hidden: true}, + {fill: '-2', hidden: true}, + {fill: '+1'}, + {fill: '+2'}, + {fill: '-1'}, + {fill: 'end', hidden: true}, + ] + }, + options: { + plugins: { + filler: { + propagate: false + } + } + } + }); - expect(decodedFillValues(chart)).toEqual([ - 'start', // 'origin' - 0, // 1 - 1 - 1, // 1 - 1, // 3 - 2 - 5, // 4 + 1 - 7, // 5 + 2 - 5, // 6 - 1 - 'end', // 'end' - ]); - }); + expect(decodedFillValues(chart)).toEqual([ + 'start', // 'origin' + 0, // 1 - 1 + 1, // 1 + 1, // 3 - 2 + 5, // 4 + 1 + 7, // 5 + 2 + 5, // 6 - 1 + 'end', // 'end' + ]); + }); - it('should prevent recursive propagation', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [ - {fill: '+2', hidden: true}, - {fill: '-1', hidden: true}, - {fill: '-1', hidden: true}, - {fill: '-2'} - ] - }, - options: { - plugins: { - filler: { - propagate: true - } - } - } - }); + it('should prevent recursive propagation', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {fill: '+2', hidden: true}, + {fill: '-1', hidden: true}, + {fill: '-1', hidden: true}, + {fill: '-2'} + ] + }, + options: { + plugins: { + filler: { + propagate: true + } + } + } + }); - expect(decodedFillValues(chart)).toEqual([ - false, // 0 + 2 -> 2 (hidden) -> 1 (hidden) -> 0 (loop) - false, // 1 - 1 -> 0 (hidden) -> 2 (hidden) -> 1 (loop) - false, // 2 - 1 -> 1 (hidden) -> 0 (hidden) -> 2 (loop) - false, // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 2 (hidden) -> 1 (loop) - ]); - }); - }); + expect(decodedFillValues(chart)).toEqual([ + false, // 0 + 2 -> 2 (hidden) -> 1 (hidden) -> 0 (loop) + false, // 1 - 1 -> 0 (hidden) -> 2 (hidden) -> 1 (loop) + false, // 2 - 1 -> 1 (hidden) -> 0 (hidden) -> 2 (loop) + false, // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 2 (hidden) -> 1 (loop) + ]); + }); + }); }); diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index a4acdf6e20b..f39d947d424 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -1,889 +1,889 @@ // Test the rectangle element describe('Legend block tests', function() { - describe('auto', jasmine.fixture.specs('plugin.legend')); - - it('should have the correct default config', function() { - expect(Chart.defaults.plugins.legend).toEqual({ - display: true, - position: 'top', - align: 'center', - fullSize: true, - reverse: false, - weight: 1000, - - // a callback that will handle - onClick: jasmine.any(Function), - onHover: null, - onLeave: null, - - labels: { - color: Chart.defaults.color, - boxWidth: 40, - padding: 10, - generateLabels: jasmine.any(Function) - }, - - title: { - color: Chart.defaults.color, - display: false, - position: 'center', - text: '', - } - }); - }); - - it('should update bar chart correctly', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'butt', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }, { - label: 'dataset2', - hidden: true, - borderJoinStyle: 'miter', - data: [] - }, { - label: 'dataset3', - borderWidth: 10, - borderColor: 'green', - pointStyle: 'crossRot', - data: [] - }], - labels: [] - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 0, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }, { - text: 'dataset2', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: true, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 0, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 1 - }, { - text: 'dataset3', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 10, - strokeStyle: 'green', - pointStyle: 'crossRot', - rotation: undefined, - datasetIndex: 2 - }]); - }); - - it('should update line chart correctly', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'round', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }, { - label: 'dataset2', - hidden: true, - borderJoinStyle: 'round', - data: [] - }, { - label: 'dataset3', - borderWidth: 10, - borderColor: 'green', - pointStyle: 'crossRot', - fill: false, - data: [] - }], - labels: [] - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: 'round', - lineDash: [2, 2], - lineDashOffset: 5.5, - lineJoin: 'miter', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }, { - text: 'dataset2', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: true, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'round', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 1 - }, { - text: 'dataset3', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'miter', - lineWidth: 10, - strokeStyle: 'green', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 2 - }]); - }); - - it('should reverse correctly', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'round', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }, { - label: 'dataset2', - hidden: true, - borderJoinStyle: 'round', - data: [] - }, { - label: 'dataset3', - borderWidth: 10, - borderColor: 'green', - pointStyle: 'crossRot', - fill: false, - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - reverse: true - } - } - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset3', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'miter', - lineWidth: 10, - strokeStyle: 'green', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 2 - }, { - text: 'dataset2', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: true, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'round', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 1 - }, { - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: 'round', - lineDash: [2, 2], - lineDashOffset: 5.5, - lineJoin: 'miter', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }]); - }); - - it('should filter items', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'butt', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }, { - label: 'dataset2', - hidden: true, - borderJoinStyle: 'miter', - data: [], - legendHidden: true - }, { - label: 'dataset3', - borderWidth: 10, - borderColor: 'green', - pointStyle: 'crossRot', - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - labels: { - filter: function(legendItem, data) { - var dataset = data.datasets[legendItem.datasetIndex]; - return !dataset.legendHidden; - } - } - } - } - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 0, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }, { - text: 'dataset3', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 10, - strokeStyle: 'green', - pointStyle: 'crossRot', - rotation: undefined, - datasetIndex: 2 - }]); - }); - - it('should sort items', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'round', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }, { - label: 'dataset2', - hidden: true, - borderJoinStyle: 'round', - data: [] - }, { - label: 'dataset3', - borderWidth: 10, - borderColor: 'green', - pointStyle: 'crossRot', - fill: false, - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - labels: { - sort: function(a, b) { - return b.datasetIndex > a.datasetIndex ? 1 : -1; - } - } - } - } - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset3', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'miter', - lineWidth: 10, - strokeStyle: 'green', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 2 - }, { - text: 'dataset2', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: true, - lineCap: 'butt', - lineDash: [], - lineDashOffset: 0, - lineJoin: 'round', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 1 - }, { - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: 'round', - lineDash: [2, 2], - lineDashOffset: 5.5, - lineJoin: 'miter', - lineWidth: 3, - strokeStyle: 'rgba(0,0,0,0.1)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }]); - }); - - it('should not throw when the label options are missing', function() { - var makeChart = function() { - window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'butt', - borderDash: [2, 2], - borderDashOffset: 5.5, - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - labels: false, - } - } - } - }); - }; - expect(makeChart).not.toThrow(); - }); - - 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: { - plugins: { - 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); - }); - }); - - it('should draw items with a custom boxHeight', function() { - var chart = window.acquireChart( - { - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - position: 'right', - labels: { - boxHeight: 40 - } - } - } - } - }, - { - canvas: { - width: 512, - height: 105 - } - } - ); - const hitBox = chart.legend.legendHitBoxes[0]; - expect(hitBox.height).toBe(40); - }); - - it('should pick up the first item when the property is an array', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: ['#f31', '#666', '#14e'], - borderWidth: [5, 10, 15], - borderColor: ['red', 'green', 'blue'], - data: [] - }], - labels: [] - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: '#f31', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 5, - strokeStyle: 'red', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }]); - }); - - it('should use the value for the first item when the property is a function', function() { - var helpers = window.Chart.helpers; - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return helpers.color({r: value * 10, g: 0, b: 0}).rgbString(); - }, - borderWidth: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return value; - }, - borderColor: function(ctx) { - var value = ctx.dataset.data[ctx.dataIndex] || 0; - return helpers.color({r: 255 - value * 10, g: 0, b: 0}).rgbString(); - }, - data: [5, 10, 15, 20] - }], - labels: ['A', 'B', 'C', 'D'] - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: 'rgb(50, 0, 0)', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 5, - strokeStyle: 'rgb(205, 0, 0)', - pointStyle: undefined, - rotation: undefined, - datasetIndex: 0 - }]); - }); - - it('should draw correctly when usePointStyle is true', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'butt', - borderDash: [2, 2], - borderDashOffset: 5.5, - borderWidth: 0, - borderColor: '#f31', - pointStyle: 'crossRot', - pointBackgroundColor: 'rgba(0,0,0,0.1)', - pointBorderWidth: 5, - pointBorderColor: 'green', - data: [] - }, { - label: 'dataset2', - backgroundColor: '#f31', - borderJoinStyle: 'miter', - borderWidth: 2, - borderColor: '#f31', - pointStyle: 'crossRot', - pointRotation: 15, - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - labels: { - usePointStyle: true - } - } - } - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 5, - strokeStyle: 'green', - pointStyle: 'crossRot', - rotation: undefined, - datasetIndex: 0 - }, { - text: 'dataset2', - fillStyle: '#f31', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 2, - strokeStyle: '#f31', - pointStyle: 'crossRot', - rotation: 15, - datasetIndex: 1 - }]); - }); - - it('should draw correctly when usePointStyle is true and pointStyle override is set', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'dataset1', - backgroundColor: '#f31', - borderCapStyle: 'butt', - borderDash: [2, 2], - borderDashOffset: 5.5, - borderWidth: 0, - borderColor: '#f31', - pointStyle: 'crossRot', - pointBackgroundColor: 'rgba(0,0,0,0.1)', - pointBorderWidth: 5, - pointBorderColor: 'green', - data: [] - }, { - label: 'dataset2', - backgroundColor: '#f31', - borderJoinStyle: 'miter', - borderWidth: 2, - borderColor: '#f31', - pointStyle: 'crossRot', - pointRotation: 15, - data: [] - }], - labels: [] - }, - options: { - plugins: { - legend: { - labels: { - usePointStyle: true, - pointStyle: 'star' - } - } - } - } - }); - - expect(chart.legend.legendItems).toEqual([{ - text: 'dataset1', - fillStyle: 'rgba(0,0,0,0.1)', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 5, - strokeStyle: 'green', - pointStyle: 'star', - rotation: undefined, - datasetIndex: 0 - }, { - text: 'dataset2', - fillStyle: '#f31', - hidden: false, - lineCap: undefined, - lineDash: undefined, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 2, - strokeStyle: '#f31', - pointStyle: 'star', - rotation: 15, - datasetIndex: 1 - }]); - }); - - it('should not crash when the legend defaults are false', function() { - const oldDefaults = Chart.defaults.plugins.legend; - - Chart.defaults.set({ - plugins: { - legend: false, - }, - }); - - var chart = window.acquireChart({ - type: 'doughnut', - data: { - datasets: [{ - label: 'dataset1', - data: [1, 2, 3, 4] - }], - labels: ['', '', '', ''] - }, - }); - expect(chart).toBeDefined(); - - Chart.defaults.set({ - plugins: { - legend: oldDefaults, - }, - }); - }); - - describe('config update', function() { - it ('should update the options', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - plugins: { - legend: { - display: true - } - } - } - }); - expect(chart.legend.options.display).toBe(true); - - chart.options.plugins.legend.display = false; - chart.update(); - expect(chart.legend.options.display).toBe(false); - }); - - it ('should update the associated layout item', function() { - var chart = acquireChart({ - type: 'line', - data: {}, - options: { - plugins: { - legend: { - fullSize: true, - position: 'top', - weight: 150 - } - } - } - }); - - expect(chart.legend.fullSize).toBe(true); - expect(chart.legend.position).toBe('top'); - expect(chart.legend.weight).toBe(150); - - chart.options.plugins.legend.fullSize = false; - chart.options.plugins.legend.position = 'left'; - chart.options.plugins.legend.weight = 42; - chart.update(); - - expect(chart.legend.fullSize).toBe(false); - expect(chart.legend.position).toBe('left'); - expect(chart.legend.weight).toBe(42); - }); - - it ('should remove the legend if the new options are false', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - } - }); - expect(chart.legend).not.toBe(undefined); - - chart.options.plugins.legend = false; - chart.update(); - expect(chart.legend).toBe(undefined); - }); - - it ('should create the legend if the legend options are changed to exist', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - plugins: { - legend: false - } - } - }); - expect(chart.legend).toBe(undefined); - - chart.options.plugins.legend = {}; - chart.update(); - expect(chart.legend).not.toBe(undefined); - expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.plugins.legend)); - }); - }); - - describe('callbacks', function() { - it('should call onClick, onHover and onLeave at the correct times', function(done) { - 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: { - plugins: { - 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) - }; - - afterEvent(chart, 'click', function() { - expect(clickItem).toBe(chart.legend.legendItems[0]); - - afterEvent(chart, 'mousemove', function() { - expect(hoverItem).toBe(chart.legend.legendItems[0]); - - afterEvent(chart, 'mousemove', function() { - expect(leaveItem).toBe(chart.legend.legendItems[0]); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', el); - }); - jasmine.triggerMouseEvent(chart, 'click', el); - }); - }); + describe('auto', jasmine.fixture.specs('plugin.legend')); + + it('should have the correct default config', function() { + expect(Chart.defaults.plugins.legend).toEqual({ + display: true, + position: 'top', + align: 'center', + fullSize: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: jasmine.any(Function), + onHover: null, + onLeave: null, + + labels: { + color: Chart.defaults.color, + boxWidth: 40, + padding: 10, + generateLabels: jasmine.any(Function) + }, + + title: { + color: Chart.defaults.color, + display: false, + position: 'center', + text: '', + } + }); + }); + + it('should update bar chart correctly', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'butt', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'miter', + data: [] + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + data: [] + }], + labels: [] + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }, { + text: 'dataset2', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: true, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 1 + }, { + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 10, + strokeStyle: 'green', + pointStyle: 'crossRot', + rotation: undefined, + datasetIndex: 2 + }]); + }); + + it('should update line chart correctly', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'round', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'round', + data: [] + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + fill: false, + data: [] + }], + labels: [] + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: 'round', + lineDash: [2, 2], + lineDashOffset: 5.5, + lineJoin: 'miter', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }, { + text: 'dataset2', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: true, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'round', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 1 + }, { + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'miter', + lineWidth: 10, + strokeStyle: 'green', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 2 + }]); + }); + + it('should reverse correctly', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'round', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'round', + data: [] + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + fill: false, + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + reverse: true + } + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'miter', + lineWidth: 10, + strokeStyle: 'green', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 2 + }, { + text: 'dataset2', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: true, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'round', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 1 + }, { + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: 'round', + lineDash: [2, 2], + lineDashOffset: 5.5, + lineJoin: 'miter', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }]); + }); + + it('should filter items', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'butt', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'miter', + data: [], + legendHidden: true + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + labels: { + filter: function(legendItem, data) { + var dataset = data.datasets[legendItem.datasetIndex]; + return !dataset.legendHidden; + } + } + } + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }, { + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 10, + strokeStyle: 'green', + pointStyle: 'crossRot', + rotation: undefined, + datasetIndex: 2 + }]); + }); + + it('should sort items', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'round', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'round', + data: [] + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + fill: false, + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + labels: { + sort: function(a, b) { + return b.datasetIndex > a.datasetIndex ? 1 : -1; + } + } + } + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'miter', + lineWidth: 10, + strokeStyle: 'green', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 2 + }, { + text: 'dataset2', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: true, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'round', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 1 + }, { + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: 'round', + lineDash: [2, 2], + lineDashOffset: 5.5, + lineJoin: 'miter', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }]); + }); + + it('should not throw when the label options are missing', function() { + var makeChart = function() { + window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'butt', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + labels: false, + } + } + } + }); + }; + expect(makeChart).not.toThrow(); + }); + + 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: { + plugins: { + 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); + }); + }); + + it('should draw items with a custom boxHeight', function() { + var chart = window.acquireChart( + { + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + position: 'right', + labels: { + boxHeight: 40 + } + } + } + } + }, + { + canvas: { + width: 512, + height: 105 + } + } + ); + const hitBox = chart.legend.legendHitBoxes[0]; + expect(hitBox.height).toBe(40); + }); + + it('should pick up the first item when the property is an array', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: ['#f31', '#666', '#14e'], + borderWidth: [5, 10, 15], + borderColor: ['red', 'green', 'blue'], + data: [] + }], + labels: [] + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 5, + strokeStyle: 'red', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }]); + }); + + it('should use the value for the first item when the property is a function', function() { + var helpers = window.Chart.helpers; + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return helpers.color({r: value * 10, g: 0, b: 0}).rgbString(); + }, + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value; + }, + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return helpers.color({r: 255 - value * 10, g: 0, b: 0}).rgbString(); + }, + data: [5, 10, 15, 20] + }], + labels: ['A', 'B', 'C', 'D'] + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: 'rgb(50, 0, 0)', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 5, + strokeStyle: 'rgb(205, 0, 0)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }]); + }); + + it('should draw correctly when usePointStyle is true', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'butt', + borderDash: [2, 2], + borderDashOffset: 5.5, + borderWidth: 0, + borderColor: '#f31', + pointStyle: 'crossRot', + pointBackgroundColor: 'rgba(0,0,0,0.1)', + pointBorderWidth: 5, + pointBorderColor: 'green', + data: [] + }, { + label: 'dataset2', + backgroundColor: '#f31', + borderJoinStyle: 'miter', + borderWidth: 2, + borderColor: '#f31', + pointStyle: 'crossRot', + pointRotation: 15, + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + labels: { + usePointStyle: true + } + } + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 5, + strokeStyle: 'green', + pointStyle: 'crossRot', + rotation: undefined, + datasetIndex: 0 + }, { + text: 'dataset2', + fillStyle: '#f31', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 2, + strokeStyle: '#f31', + pointStyle: 'crossRot', + rotation: 15, + datasetIndex: 1 + }]); + }); + + it('should draw correctly when usePointStyle is true and pointStyle override is set', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'butt', + borderDash: [2, 2], + borderDashOffset: 5.5, + borderWidth: 0, + borderColor: '#f31', + pointStyle: 'crossRot', + pointBackgroundColor: 'rgba(0,0,0,0.1)', + pointBorderWidth: 5, + pointBorderColor: 'green', + data: [] + }, { + label: 'dataset2', + backgroundColor: '#f31', + borderJoinStyle: 'miter', + borderWidth: 2, + borderColor: '#f31', + pointStyle: 'crossRot', + pointRotation: 15, + data: [] + }], + labels: [] + }, + options: { + plugins: { + legend: { + labels: { + usePointStyle: true, + pointStyle: 'star' + } + } + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset1', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 5, + strokeStyle: 'green', + pointStyle: 'star', + rotation: undefined, + datasetIndex: 0 + }, { + text: 'dataset2', + fillStyle: '#f31', + hidden: false, + lineCap: undefined, + lineDash: undefined, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 2, + strokeStyle: '#f31', + pointStyle: 'star', + rotation: 15, + datasetIndex: 1 + }]); + }); + + it('should not crash when the legend defaults are false', function() { + const oldDefaults = Chart.defaults.plugins.legend; + + Chart.defaults.set({ + plugins: { + legend: false, + }, + }); + + var chart = window.acquireChart({ + type: 'doughnut', + data: { + datasets: [{ + label: 'dataset1', + data: [1, 2, 3, 4] + }], + labels: ['', '', '', ''] + }, + }); + expect(chart).toBeDefined(); + + Chart.defaults.set({ + plugins: { + legend: oldDefaults, + }, + }); + }); + + describe('config update', function() { + it ('should update the options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + plugins: { + legend: { + display: true + } + } + } + }); + expect(chart.legend.options.display).toBe(true); + + chart.options.plugins.legend.display = false; + chart.update(); + expect(chart.legend.options.display).toBe(false); + }); + + it ('should update the associated layout item', function() { + var chart = acquireChart({ + type: 'line', + data: {}, + options: { + plugins: { + legend: { + fullSize: true, + position: 'top', + weight: 150 + } + } + } + }); + + expect(chart.legend.fullSize).toBe(true); + expect(chart.legend.position).toBe('top'); + expect(chart.legend.weight).toBe(150); + + chart.options.plugins.legend.fullSize = false; + chart.options.plugins.legend.position = 'left'; + chart.options.plugins.legend.weight = 42; + chart.update(); + + expect(chart.legend.fullSize).toBe(false); + expect(chart.legend.position).toBe('left'); + expect(chart.legend.weight).toBe(42); + }); + + it ('should remove the legend if the new options are false', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + } + }); + expect(chart.legend).not.toBe(undefined); + + chart.options.plugins.legend = false; + chart.update(); + expect(chart.legend).toBe(undefined); + }); + + it ('should create the legend if the legend options are changed to exist', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + plugins: { + legend: false + } + } + }); + expect(chart.legend).toBe(undefined); + + chart.options.plugins.legend = {}; + chart.update(); + expect(chart.legend).not.toBe(undefined); + expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.plugins.legend)); + }); + }); + + describe('callbacks', function() { + it('should call onClick, onHover and onLeave at the correct times', function(done) { + 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: { + plugins: { + 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) + }; + + afterEvent(chart, 'click', function() { + expect(clickItem).toBe(chart.legend.legendItems[0]); + + afterEvent(chart, 'mousemove', function() { + expect(hoverItem).toBe(chart.legend.legendItems[0]); + + afterEvent(chart, 'mousemove', function() { + expect(leaveItem).toBe(chart.legend.legendItems[0]); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', el); + }); + jasmine.triggerMouseEvent(chart, 'click', el); + }); + }); }); diff --git a/test/specs/plugin.title.tests.js b/test/specs/plugin.title.tests.js index 351e1dcd527..9bcd0e802c7 100644 --- a/test/specs/plugin.title.tests.js +++ b/test/specs/plugin.title.tests.js @@ -3,354 +3,354 @@ var Title = Chart.registry.getPlugin('title')._element; describe('Title block tests', function() { - it('Should have the correct default config', function() { - expect(Chart.defaults.plugins.title).toEqual({ - align: 'center', - color: Chart.defaults.color, - display: false, - position: 'top', - fullSize: true, - weight: 2000, - font: { - style: 'bold' - }, - padding: 10, - text: '' - }); - }); - - it('should update correctly', function() { - var chart = { - options: Chart.helpers.clone(Chart.defaults) - }; - - var options = Chart.helpers.clone(Chart.defaults.plugins.title); - options.text = 'My title'; - - var title = new Title({ - chart: chart, - options: options - }); - - title.update(400, 200); - - expect(title.width).toEqual(0); - expect(title.height).toEqual(0); - - // Now we have a height since we display - title.options.display = true; - - title.update(400, 200); - - expect(title.width).toEqual(400); - expect(title.height).toEqual(34.4); - }); - - it('should update correctly when vertical', function() { - var chart = { - options: Chart.helpers.clone(Chart.defaults) - }; - - var options = Chart.helpers.clone(Chart.defaults.plugins.title); - options.text = 'My title'; - options.position = 'left'; - - var title = new Title({ - chart: chart, - options: options - }); - - title.update(200, 400); - - expect(title.width).toEqual(0); - expect(title.height).toEqual(0); - - // Now we have a height since we display - title.options.display = true; - - title.update(200, 400); - - expect(title.width).toEqual(34.4); - expect(title.height).toEqual(400); - }); - - it('should have the correct size when there are multiple lines of text', function() { - var chart = { - options: Chart.helpers.clone(Chart.defaults) - }; - - var options = Chart.helpers.clone(Chart.defaults.plugins.title); - options.text = ['line1', 'line2']; - options.position = 'left'; - options.display = true; - options.font.lineHeight = 1.5; - - var title = new Title({ - chart: chart, - options: options - }); - - title.update(200, 400); - - expect(title.width).toEqual(56); - expect(title.height).toEqual(400); - }); - - it('should draw correctly horizontally', function() { - var chart = { - options: Chart.helpers.clone(Chart.defaults) - }; - var context = window.createMockContext(); - - var options = Chart.helpers.clone(Chart.defaults.plugins.title); - options.text = 'My title'; - - var title = new Title({ - chart: chart, - options: options, - ctx: context - }); - - title.update(400, 200); - title.draw(); - - expect(context.getCalls()).toEqual([]); - - // Now we have a height since we display - title.options.display = true; - - title.update(400, 200); - title.top = 50; - title.left = 100; - title.bottom = title.top + title.height; - title.right = title.left + title.width; - title.draw(); - - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [] - }, { - name: 'translate', - args: [300, 67.2] - }, { - name: 'rotate', - args: [0] - }, { - name: 'setFont', - args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], - }, { - name: 'setFillStyle', - args: ['#666'] - }, { - name: 'setTextAlign', - args: ['center'], - }, { - name: 'setTextBaseline', - args: ['middle'], - }, { - name: 'fillText', - args: ['My title', 0, 0, 400] - }, { - name: 'restore', - args: [] - }]); - }); - - it ('should draw correctly vertically', function() { - var chart = { - options: Chart.helpers.clone(Chart.defaults) - }; - var context = window.createMockContext(); - - var options = Chart.helpers.clone(Chart.defaults.plugins.title); - options.text = 'My title'; - options.position = 'left'; - - var title = new Title({ - chart: chart, - options: options, - ctx: context - }); - - title.update(200, 400); - title.draw(); - - expect(context.getCalls()).toEqual([]); - - // Now we have a height since we display - title.options.display = true; - - title.update(200, 400); - title.top = 50; - title.left = 100; - title.bottom = title.top + title.height; - title.right = title.left + title.width; - title.draw(); - - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [] - }, { - name: 'translate', - args: [117.2, 250] - }, { - name: 'rotate', - args: [-0.5 * Math.PI] - }, { - name: 'setFont', - args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], - }, { - name: 'setFillStyle', - args: ['#666'] - }, { - name: 'setTextAlign', - args: ['center'], - }, { - name: 'setTextBaseline', - args: ['middle'], - }, { - name: 'fillText', - args: ['My title', 0, 0, 400] - }, { - name: 'restore', - args: [] - }]); - - // Rotation is other way on right side - title.options.position = 'right'; - - // Reset call tracker - context.resetCalls(); - - title.update(200, 400); - title.top = 50; - title.left = 100; - title.bottom = title.top + title.height; - title.right = title.left + title.width; - title.draw(); - - expect(context.getCalls()).toEqual([{ - name: 'save', - args: [] - }, { - name: 'translate', - args: [117.2, 250] - }, { - name: 'rotate', - args: [0.5 * Math.PI] - }, { - name: 'setFont', - args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], - }, { - name: 'setFillStyle', - args: ['#666'] - }, { - name: 'setTextAlign', - args: ['center'], - }, { - name: 'setTextBaseline', - args: ['middle'], - }, { - name: 'fillText', - args: ['My title', 0, 0, 400] - }, { - name: 'restore', - args: [] - }]); - }); - - describe('config update', function() { - it ('should update the options', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - plugins: { - title: { - display: true - } - } - } - }); - expect(chart.titleBlock.options.display).toBe(true); - - chart.options.plugins.title.display = false; - chart.update(); - expect(chart.titleBlock.options.display).toBe(false); - }); - - it ('should update the associated layout item', function() { - var chart = acquireChart({ - type: 'line', - data: {}, - options: { - plugins: { - title: { - fullSize: true, - position: 'top', - weight: 150 - } - } - } - }); - - expect(chart.titleBlock.fullSize).toBe(true); - expect(chart.titleBlock.position).toBe('top'); - expect(chart.titleBlock.weight).toBe(150); - - chart.options.plugins.title.fullSize = false; - chart.options.plugins.title.position = 'left'; - chart.options.plugins.title.weight = 42; - chart.update(); - - expect(chart.titleBlock.fullSize).toBe(false); - expect(chart.titleBlock.position).toBe('left'); - expect(chart.titleBlock.weight).toBe(42); - }); - - it ('should remove the title if the new options are false', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - } - }); - expect(chart.titleBlock).not.toBe(undefined); - - chart.options.plugins.title = false; - chart.update(); - expect(chart.titleBlock).toBe(undefined); - }); - - it ('should create the title if the title options are changed to exist', function() { - var chart = acquireChart({ - type: 'line', - data: { - labels: ['A', 'B', 'C', 'D'], - datasets: [{ - data: [10, 20, 30, 100] - }] - }, - options: { - plugins: { - title: false - } - } - }); - expect(chart.titleBlock).toBe(undefined); - - chart.options.plugins.title = {}; - chart.update(); - expect(chart.titleBlock).not.toBe(undefined); - expect(chart.titleBlock.options).toEqual(jasmine.objectContaining(Chart.defaults.plugins.title)); - }); - }); + it('Should have the correct default config', function() { + expect(Chart.defaults.plugins.title).toEqual({ + align: 'center', + color: Chart.defaults.color, + display: false, + position: 'top', + fullSize: true, + weight: 2000, + font: { + style: 'bold' + }, + padding: 10, + text: '' + }); + }); + + it('should update correctly', function() { + var chart = { + options: Chart.helpers.clone(Chart.defaults) + }; + + var options = Chart.helpers.clone(Chart.defaults.plugins.title); + options.text = 'My title'; + + var title = new Title({ + chart: chart, + options: options + }); + + title.update(400, 200); + + expect(title.width).toEqual(0); + expect(title.height).toEqual(0); + + // Now we have a height since we display + title.options.display = true; + + title.update(400, 200); + + expect(title.width).toEqual(400); + expect(title.height).toEqual(34.4); + }); + + it('should update correctly when vertical', function() { + var chart = { + options: Chart.helpers.clone(Chart.defaults) + }; + + var options = Chart.helpers.clone(Chart.defaults.plugins.title); + options.text = 'My title'; + options.position = 'left'; + + var title = new Title({ + chart: chart, + options: options + }); + + title.update(200, 400); + + expect(title.width).toEqual(0); + expect(title.height).toEqual(0); + + // Now we have a height since we display + title.options.display = true; + + title.update(200, 400); + + expect(title.width).toEqual(34.4); + expect(title.height).toEqual(400); + }); + + it('should have the correct size when there are multiple lines of text', function() { + var chart = { + options: Chart.helpers.clone(Chart.defaults) + }; + + var options = Chart.helpers.clone(Chart.defaults.plugins.title); + options.text = ['line1', 'line2']; + options.position = 'left'; + options.display = true; + options.font.lineHeight = 1.5; + + var title = new Title({ + chart: chart, + options: options + }); + + title.update(200, 400); + + expect(title.width).toEqual(56); + expect(title.height).toEqual(400); + }); + + it('should draw correctly horizontally', function() { + var chart = { + options: Chart.helpers.clone(Chart.defaults) + }; + var context = window.createMockContext(); + + var options = Chart.helpers.clone(Chart.defaults.plugins.title); + options.text = 'My title'; + + var title = new Title({ + chart: chart, + options: options, + ctx: context + }); + + title.update(400, 200); + title.draw(); + + expect(context.getCalls()).toEqual([]); + + // Now we have a height since we display + title.options.display = true; + + title.update(400, 200); + title.top = 50; + title.left = 100; + title.bottom = title.top + title.height; + title.right = title.left + title.width; + title.draw(); + + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [] + }, { + name: 'translate', + args: [300, 67.2] + }, { + name: 'rotate', + args: [0] + }, { + name: 'setFont', + args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], + }, { + name: 'setFillStyle', + args: ['#666'] + }, { + name: 'setTextAlign', + args: ['center'], + }, { + name: 'setTextBaseline', + args: ['middle'], + }, { + name: 'fillText', + args: ['My title', 0, 0, 400] + }, { + name: 'restore', + args: [] + }]); + }); + + it ('should draw correctly vertically', function() { + var chart = { + options: Chart.helpers.clone(Chart.defaults) + }; + var context = window.createMockContext(); + + var options = Chart.helpers.clone(Chart.defaults.plugins.title); + options.text = 'My title'; + options.position = 'left'; + + var title = new Title({ + chart: chart, + options: options, + ctx: context + }); + + title.update(200, 400); + title.draw(); + + expect(context.getCalls()).toEqual([]); + + // Now we have a height since we display + title.options.display = true; + + title.update(200, 400); + title.top = 50; + title.left = 100; + title.bottom = title.top + title.height; + title.right = title.left + title.width; + title.draw(); + + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [] + }, { + name: 'translate', + args: [117.2, 250] + }, { + name: 'rotate', + args: [-0.5 * Math.PI] + }, { + name: 'setFont', + args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], + }, { + name: 'setFillStyle', + args: ['#666'] + }, { + name: 'setTextAlign', + args: ['center'], + }, { + name: 'setTextBaseline', + args: ['middle'], + }, { + name: 'fillText', + args: ['My title', 0, 0, 400] + }, { + name: 'restore', + args: [] + }]); + + // Rotation is other way on right side + title.options.position = 'right'; + + // Reset call tracker + context.resetCalls(); + + title.update(200, 400); + title.top = 50; + title.left = 100; + title.bottom = title.top + title.height; + title.right = title.left + title.width; + title.draw(); + + expect(context.getCalls()).toEqual([{ + name: 'save', + args: [] + }, { + name: 'translate', + args: [117.2, 250] + }, { + name: 'rotate', + args: [0.5 * Math.PI] + }, { + name: 'setFont', + args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"], + }, { + name: 'setFillStyle', + args: ['#666'] + }, { + name: 'setTextAlign', + args: ['center'], + }, { + name: 'setTextBaseline', + args: ['middle'], + }, { + name: 'fillText', + args: ['My title', 0, 0, 400] + }, { + name: 'restore', + args: [] + }]); + }); + + describe('config update', function() { + it ('should update the options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + plugins: { + title: { + display: true + } + } + } + }); + expect(chart.titleBlock.options.display).toBe(true); + + chart.options.plugins.title.display = false; + chart.update(); + expect(chart.titleBlock.options.display).toBe(false); + }); + + it ('should update the associated layout item', function() { + var chart = acquireChart({ + type: 'line', + data: {}, + options: { + plugins: { + title: { + fullSize: true, + position: 'top', + weight: 150 + } + } + } + }); + + expect(chart.titleBlock.fullSize).toBe(true); + expect(chart.titleBlock.position).toBe('top'); + expect(chart.titleBlock.weight).toBe(150); + + chart.options.plugins.title.fullSize = false; + chart.options.plugins.title.position = 'left'; + chart.options.plugins.title.weight = 42; + chart.update(); + + expect(chart.titleBlock.fullSize).toBe(false); + expect(chart.titleBlock.position).toBe('left'); + expect(chart.titleBlock.weight).toBe(42); + }); + + it ('should remove the title if the new options are false', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + } + }); + expect(chart.titleBlock).not.toBe(undefined); + + chart.options.plugins.title = false; + chart.update(); + expect(chart.titleBlock).toBe(undefined); + }); + + it ('should create the title if the title options are changed to exist', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + plugins: { + title: false + } + } + }); + expect(chart.titleBlock).toBe(undefined); + + chart.options.plugins.title = {}; + chart.update(); + expect(chart.titleBlock).not.toBe(undefined); + expect(chart.titleBlock.options).toEqual(jasmine.objectContaining(Chart.defaults.plugins.title)); + }); + }); }); diff --git a/test/specs/plugin.tooltip.tests.js b/test/specs/plugin.tooltip.tests.js index fabb34d3874..2166ee645f5 100644 --- a/test/specs/plugin.tooltip.tests.js +++ b/test/specs/plugin.tooltip.tests.js @@ -3,1576 +3,1576 @@ const tooltipPlugin = Chart.registry.getPlugin('tooltip'); const Tooltip = tooltipPlugin._element; describe('Plugin.Tooltip', function() { - describe('auto', jasmine.fixture.specs('core.tooltip')); - - describe('config', function() { - it('should not include the dataset label in the body string if not defined', function() { - var data = { - datasets: [{ - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }; - var tooltipItem = { - index: 1, - datasetIndex: 0, - dataset: data.datasets[0], - label: 'Point 2', - formattedValue: '20' - }; - - var label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem); - expect(label).toBe('20'); - - data.datasets[0].label = 'My dataset'; - label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem); - expect(label).toBe('My dataset: 20'); - }); - }); - - describe('index mode', function() { - it('Should only use x distance when intersect is false', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - intersect: false, - } - }, - hover: { - mode: 'index', - intersect: false - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip.options.xPadding).toEqual(6); - expect(tooltip.options.yPadding).toEqual(6); - expect(tooltip.xAlign).toEqual('left'); - expect(tooltip.yAlign).toEqual('center'); - expect(tooltip.options.bodyColor).toEqual('#fff'); - - expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: defaults.font.style, - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - bodyAlign: 'left', - bodySpacing: 2, - })); - - expect(tooltip.options.titleColor).toEqual('#fff'); - expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - titleAlign: 'left', - titleSpacing: 2, - titleMarginBottom: 6, - })); - - expect(tooltip.options.footerColor).toEqual('#fff'); - expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - footerAlign: 'left', - footerSpacing: 2, - footerMarginTop: 6, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - // Appearance - caretSize: 5, - caretPadding: 2, - cornerRadius: 6, - backgroundColor: 'rgba(0,0,0,0.8)', - multiKeyBackground: '#fff', - displayColors: true - })); - - expect(tooltip).toEqual(jasmine.objectContaining({ - opacity: 1, - - // Text - title: ['Point 2'], - beforeBody: [], - body: [{ - before: [], - lines: ['Dataset 1: 20'], - after: [] - }, { - before: [], - lines: ['Dataset 2: 40'], - after: [] - }], - afterBody: [], - footer: [], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(155); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: chart.chartArea.top + 10}); - }); - - it('Should only display if intersecting if intersect is set', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - intersect: true - } - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - - expect(tooltip).toEqual(jasmine.objectContaining({ - opacity: 0, - })); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: 0}); - }); - }); - - it('Should display in single mode', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'nearest', - intersect: true - } - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip.options.xPadding).toEqual(6); - expect(tooltip.options.yPadding).toEqual(6); - expect(tooltip.xAlign).toEqual('left'); - expect(tooltip.yAlign).toEqual('center'); - - expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: defaults.font.style, - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - bodyAlign: 'left', - bodySpacing: 2, - })); - - expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - titleAlign: 'left', - titleSpacing: 2, - titleMarginBottom: 6, - })); - - expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - footerAlign: 'left', - footerSpacing: 2, - footerMarginTop: 6, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - // Appearance - caretSize: 5, - caretPadding: 2, - cornerRadius: 6, - backgroundColor: 'rgba(0,0,0,0.8)', - multiKeyBackground: '#fff', - displayColors: true - })); - - expect(tooltip.opacity).toEqual(1); - expect(tooltip.title).toEqual(['Point 2']); - expect(tooltip.beforeBody).toEqual([]); - expect(tooltip.body).toEqual([{ - before: [], - lines: ['Dataset 1: 20'], - after: [] - }]); - expect(tooltip.afterBody).toEqual([]); - expect(tooltip.footer).toEqual([]); - expect(tooltip.labelTextColors).toEqual(['#fff']); - - expect(tooltip.labelColors).toEqual([{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }]); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(312); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it('Should display information from user callbacks', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - callbacks: { - beforeTitle: function() { - return 'beforeTitle'; - }, - title: function() { - return 'title'; - }, - afterTitle: function() { - return 'afterTitle'; - }, - beforeBody: function() { - return 'beforeBody'; - }, - beforeLabel: function() { - return 'beforeLabel'; - }, - label: function() { - return 'label'; - }, - afterLabel: function() { - return 'afterLabel'; - }, - afterBody: function() { - return 'afterBody'; - }, - beforeFooter: function() { - return 'beforeFooter'; - }, - footer: function() { - return 'footer'; - }, - afterFooter: function() { - return 'afterFooter'; - }, - labelTextColor: function() { - return 'labelTextColor'; - }, - labelPointStyle: function() { - return { - pointStyle: 'labelPointStyle', - rotation: 42 - }; - } - } - } - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip.options.xPadding).toEqual(6); - expect(tooltip.options.yPadding).toEqual(6); - expect(tooltip.xAlign).toEqual('left'); - expect(tooltip.yAlign).toEqual('center'); - - expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: defaults.font.style, - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - bodyAlign: 'left', - bodySpacing: 2, - })); - - expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - titleSpacing: 2, - titleMarginBottom: 6, - })); - - expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - footerAlign: 'left', - footerSpacing: 2, - footerMarginTop: 6, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - // Appearance - caretSize: 5, - caretPadding: 2, - cornerRadius: 6, - backgroundColor: 'rgba(0,0,0,0.8)', - multiKeyBackground: '#fff', - })); - - expect(tooltip).toEqual(jasmine.objectContaining({ - opacity: 1, - - // Text - title: ['beforeTitle', 'title', 'afterTitle'], - beforeBody: ['beforeBody'], - body: [{ - before: ['beforeLabel'], - lines: ['label'], - after: ['afterLabel'] - }, { - before: ['beforeLabel'], - lines: ['label'], - after: ['afterLabel'] - }], - afterBody: ['afterBody'], - footer: ['beforeFooter', 'footer', 'afterFooter'], - labelTextColors: ['labelTextColor', 'labelTextColor'], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }], - labelPointStyles: [{ - pointStyle: 'labelPointStyle', - rotation: 42 - }, { - pointStyle: 'labelPointStyle', - rotation: 42 - }] - })); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(75); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - - it('Should provide context object to user callbacks', function(done) { - const chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [{x: 1, y: 10}, {x: 2, y: 20}, {x: 3, y: 30}] - }] - }, - options: { - scales: { - x: { - type: 'linear' - } - }, - plugins: { - tooltip: { - mode: 'index', - callbacks: { - beforeLabel: function(ctx) { - return ctx.parsed.x + ',' + ctx.parsed.y; - } - } - } - } - } - }); - - // Trigger an event over top of the - const meta = chart.getDatasetMeta(0); - const point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - const tooltip = chart.tooltip; - - expect(tooltip.body[0].before).toEqual(['2,20']); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - it('Should allow sorting items', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - itemSort: function(a, b) { - return a.datasetIndex > b.datasetIndex ? -1 : 1; - } - } - } - } - }); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var point0 = meta0.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip).toEqual(jasmine.objectContaining({ - // Positioning - xAlign: 'left', - yAlign: 'center', - - // Text - title: ['Point 2'], - beforeBody: [], - body: [{ - before: [], - lines: ['Dataset 2: 40'], - after: [] - }, { - before: [], - lines: ['Dataset 1: 20'], - after: [] - }], - afterBody: [], - footer: [], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(155); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point0); - - }); - - it('Should allow reversing items', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - reverse: true - } - } - } - }); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var point0 = meta0.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip).toEqual(jasmine.objectContaining({ - // Positioning - xAlign: 'left', - yAlign: 'center', - - // Text - title: ['Point 2'], - beforeBody: [], - body: [{ - before: [], - lines: ['Dataset 2: 40'], - after: [] - }, { - before: [], - lines: ['Dataset 1: 20'], - after: [] - }], - afterBody: [], - footer: [], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(155); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point0); - }); - - it('Should follow dataset order', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)', - order: 10 - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)', - order: 5 - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index' - } - } - } - }); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var point0 = meta0.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip).toEqual(jasmine.objectContaining({ - // Positioning - xAlign: 'left', - yAlign: 'center', - - // Text - title: ['Point 2'], - beforeBody: [], - body: [{ - before: [], - lines: ['Dataset 2: 40'], - after: [] - }, { - before: [], - lines: ['Dataset 1: 20'], - after: [] - }], - afterBody: [], - footer: [], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - expect(tooltip.x).toBeCloseToPixel(267); - expect(tooltip.y).toBeCloseToPixel(155); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point0); - }); - - it('should filter items from the tooltip using the callback', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)', - tooltipHidden: true - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - filter: function(tooltipItem, index, tooltipItems, data) { - // For testing purposes remove the first dataset that has a tooltipHidden property - return !data.datasets[tooltipItem.datasetIndex].tooltipHidden; - } - } - } - } - }); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var point0 = meta0.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip).toEqual(jasmine.objectContaining({ - // Positioning - xAlign: 'left', - yAlign: 'center', - - // Text - title: ['Point 2'], - beforeBody: [], - body: [{ - before: [], - lines: ['Dataset 2: 40'], - after: [] - }], - afterBody: [], - footer: [], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point0); - }); - - it('should set the caretPadding based on a config setting', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)', - tooltipHidden: true - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - caretPadding: 10 - } - } - } - }); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var point0 = meta0.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - // Positioning - caretPadding: 10, - })); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point0); - }); - - ['line', 'bar'].forEach(function(type) { - it('Should have dataPoints in a ' + type + ' chart', function(done) { - var chart = window.acquireChart({ - type: type, - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'nearest', - intersect: true - } - } - } - }); - - // Trigger an event over top of the element - var pointIndex = 1; - var datasetIndex = 0; - var point = chart.getDatasetMeta(datasetIndex).data[pointIndex]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - - expect(tooltip instanceof Object).toBe(true); - expect(tooltip.dataPoints instanceof Array).toBe(true); - expect(tooltip.dataPoints.length).toBe(1); - - var tooltipItem = tooltip.dataPoints[0]; - - expect(tooltipItem.dataIndex).toBe(pointIndex); - expect(tooltipItem.datasetIndex).toBe(datasetIndex); - expect(typeof tooltipItem.label).toBe('string'); - expect(tooltipItem.label).toBe(chart.data.labels[pointIndex]); - expect(typeof tooltipItem.formattedValue).toBe('string'); - expect(tooltipItem.formattedValue).toBe('' + chart.data.datasets[datasetIndex].data[pointIndex]); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); - - it('Should not update if active element has not changed', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'nearest', - intersect: true, - callbacks: { - title: function() { - return 'registering callback...'; - } - } - } - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var firstPoint = meta.data[1]; - - var tooltip = chart.tooltip; - spyOn(tooltip, 'update').and.callThrough(); - - afterEvent(chart, 'mousemove', function() { - expect(tooltip.update).toHaveBeenCalledWith(true); - - // Reset calls - tooltip.update.calls.reset(); - - afterEvent(chart, 'mousemove', function() { - expect(tooltip.update).not.toHaveBeenCalled(); - - done(); - }); - // Second dispatch change event (same event), should not update tooltip - jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); - }); - // First dispatch change event, should update tooltip - jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); - }); - - it('Should update if active elements are the same, but the position has changed', function(done) { - const chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - scales: { - x: { - stacked: true, - }, - y: { - stacked: true - } - }, - plugins: { - tooltip: { - mode: 'nearest', - position: 'nearest', - intersect: true, - callbacks: { - title: function() { - return 'registering callback...'; - } - } - } - } - } - }); - - // Trigger an event over top of the - const meta = chart.getDatasetMeta(0); - const firstPoint = meta.data[1]; - - const meta2 = chart.getDatasetMeta(1); - const secondPoint = meta2.data[1]; - - const tooltip = chart.tooltip; - spyOn(tooltip, 'update'); - - afterEvent(chart, 'mousemove', function() { - expect(tooltip.update).toHaveBeenCalledWith(true); - - // Reset calls - tooltip.update.calls.reset(); - - afterEvent(chart, 'mousemove', function() { - expect(tooltip.update).toHaveBeenCalledWith(true); - - done(); - }); - // Second dispatch change event (same event), should update tooltip - // because position mode is 'nearest' - jasmine.triggerMouseEvent(chart, 'mousemove', secondPoint); - }); - // First dispatch change event, should update tooltip - jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); - }); - - describe('positioners', function() { - it('Should call custom positioner with correct parameters and scope', function(done) { - - tooltipPlugin.positioners.test = function() { - return {x: 0, y: 0}; - }; - - spyOn(tooltipPlugin.positioners, 'test').and.callThrough(); - - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'nearest', - position: 'test' - } - } - } - }); - - // Trigger an event over top of the - var pointIndex = 1; - var datasetIndex = 0; - var meta = chart.getDatasetMeta(datasetIndex); - var point = meta.data[pointIndex]; - var fn = tooltipPlugin.positioners.test; - - afterEvent(chart, 'mousemove', function() { - expect(fn.calls.count()).toBe(2); - expect(fn.calls.first().args[0] instanceof Array).toBe(true); - expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'x')).toBe(true); - expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'y')).toBe(true); - expect(fn.calls.first().object instanceof Tooltip).toBe(true); - - done(); - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - }); - - it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', function(done) { - var chart = window.acquireChart({ - type: 'pie', - data: { - datasets: [{ - data: [ - 50, - 50 - ], - backgroundColor: [ - 'rgb(255, 0, 0)', - 'rgb(0, 255, 0)' - ], - label: 'Dataset 1' - }], - labels: [ - 'Red long tooltip text to avoid unnecessary loop steps', - 'Green long tooltip text to avoid unnecessary loop steps' - ] - }, - options: { - responsive: true, - animation: { - // without this slice center point is calculated wrong - animateRotate: false - }, - plugins: { - tooltip: { - animation: false - } - } - } - }); - - function testSlice(slice, count) { - var meta = chart.getDatasetMeta(0); - var point = meta.data[slice].getCenterPoint(); - var tooltipPosition = meta.data[slice].tooltipPosition(); - - function recursive(left) { - chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'XX'; - chart.update(); - - afterEvent(chart, 'mouseout', function() { - afterEvent(chart, 'mousemove', function() { - var tooltip = chart.tooltip; - expect(tooltip.dataPoints.length).toBe(1); - expect(tooltip.x).toBeGreaterThanOrEqual(0); - if (tooltip.width <= chart.width) { - expect(tooltip.x + tooltip.width).toBeLessThanOrEqual(chart.width); - } - expect(tooltip.caretX).toBeCloseToPixel(tooltipPosition.x); - // if tooltip is longer than chart area then all tests done - if (tooltip.width > chart.width || left === 0) { - done(left === 0 && new Error('max iterations reached')); - } else { - recursive(left - 1); - } - }); - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - jasmine.triggerMouseEvent(chart, 'mouseout', point); - } - - recursive(count); - } - - // Trigger an event over top of the slice - for (var slice = 0; slice < 2; slice++) { - testSlice(slice, 20); - } - }); - - it('Should split newlines into separate lines in user callbacks', function(done) { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }, { - label: 'Dataset 2', - data: [40, 40, 40], - pointHoverBorderColor: 'rgb(0, 0, 255)', - pointHoverBackgroundColor: 'rgb(0, 255, 255)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - options: { - plugins: { - tooltip: { - mode: 'index', - callbacks: { - beforeTitle: function() { - return 'beforeTitle\nnewline'; - }, - title: function() { - return 'title\nnewline'; - }, - afterTitle: function() { - return 'afterTitle\nnewline'; - }, - beforeBody: function() { - return 'beforeBody\nnewline'; - }, - beforeLabel: function() { - return 'beforeLabel\nnewline'; - }, - label: function() { - return 'label'; - }, - afterLabel: function() { - return 'afterLabel\nnewline'; - }, - afterBody: function() { - return 'afterBody\nnewline'; - }, - beforeFooter: function() { - return 'beforeFooter\nnewline'; - }, - footer: function() { - return 'footer\nnewline'; - }, - afterFooter: function() { - return 'afterFooter\nnewline'; - }, - labelTextColor: function() { - return 'labelTextColor'; - } - } - } - } - } - }); - - // Trigger an event over top of the - var meta = chart.getDatasetMeta(0); - var point = meta.data[1]; - - afterEvent(chart, 'mousemove', function() { - // Check and see if tooltip was displayed - var tooltip = chart.tooltip; - var defaults = Chart.defaults; - - expect(tooltip.options.xPadding).toEqual(6); - expect(tooltip.options.yPadding).toEqual(6); - expect(tooltip.xAlign).toEqual('center'); - expect(tooltip.yAlign).toEqual('top'); - - expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: defaults.font.style, - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - bodyAlign: 'left', - bodySpacing: 2, - })); - - expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - titleAlign: 'left', - titleSpacing: 2, - titleMarginBottom: 6, - })); - - expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - footerAlign: 'left', - footerSpacing: 2, - footerMarginTop: 6, - })); - - expect(tooltip.options).toEqual(jasmine.objectContaining({ - // Appearance - caretSize: 5, - caretPadding: 2, - cornerRadius: 6, - backgroundColor: 'rgba(0,0,0,0.8)', - multiKeyBackground: '#fff', - })); - - expect(tooltip).toEqual(jasmine.objectContaining({ - opacity: 1, - - // Text - title: ['beforeTitle', 'newline', 'title', 'newline', 'afterTitle', 'newline'], - beforeBody: ['beforeBody', 'newline'], - body: [{ - before: ['beforeLabel', 'newline'], - lines: ['label'], - after: ['afterLabel', 'newline'] - }, { - before: ['beforeLabel', 'newline'], - lines: ['label'], - after: ['afterLabel', 'newline'] - }], - afterBody: ['afterBody', 'newline'], - footer: ['beforeFooter', 'newline', 'footer', 'newline', 'afterFooter', 'newline'], - labelTextColors: ['labelTextColor', 'labelTextColor'], - labelColors: [{ - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }, { - borderColor: defaults.borderColor, - backgroundColor: defaults.backgroundColor - }] - })); - - done(); - }); - - jasmine.triggerMouseEvent(chart, 'mousemove', point); - }); - - describe('text align', function() { - var defaults = Chart.defaults; - var makeView = function(title, body, footer) { - return { - // Positioning - x: 100, - y: 100, - width: 100, - height: 100, - xAlign: 'left', - yAlign: 'top', - - options: { - enabled: true, - - xPadding: 5, - yPadding: 5, - - // Body - bodyFont: { - family: defaults.font.family, - style: defaults.font.style, - size: defaults.font.size, - }, - bodyColor: '#fff', - bodyAlign: body, - bodySpacing: 2, - - // Title - titleFont: { - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - }, - titleColor: '#fff', - titleAlign: title, - titleSpacing: 2, - titleMarginBottom: 6, - - // Footer - footerFont: { - family: defaults.font.family, - style: 'bold', - size: defaults.font.size, - }, - footerColor: '#fff', - footerAlign: footer, - footerSpacing: 2, - footerMarginTop: 6, - - // Appearance - caretSize: 5, - cornerRadius: 6, - caretPadding: 2, - borderColor: '#aaa', - borderWidth: 1, - backgroundColor: 'rgba(0,0,0,0.8)', - multiKeyBackground: '#fff', - displayColors: false - - }, - opacity: 1, - - // Text - title: ['title'], - beforeBody: [], - body: [{ - before: [], - lines: ['label'], - after: [] - }], - afterBody: [], - footer: ['footer'], - labelTextColors: ['#fff'], - labelColors: [{ - borderColor: 'rgb(255, 0, 0)', - backgroundColor: 'rgb(0, 255, 0)' - }, { - borderColor: 'rgb(0, 0, 255)', - backgroundColor: 'rgb(0, 255, 255)' - }] - }; - }; - var drawBody = [ - {name: 'save', args: []}, - {name: 'setFillStyle', args: ['rgba(0,0,0,0.8)']}, - {name: 'setStrokeStyle', args: ['#aaa']}, - {name: 'setLineWidth', args: [1]}, - {name: 'beginPath', args: []}, - {name: 'moveTo', args: [106, 100]}, - {name: 'lineTo', args: [106, 100]}, - {name: 'lineTo', args: [111, 95]}, - {name: 'lineTo', args: [116, 100]}, - {name: 'lineTo', args: [194, 100]}, - {name: 'quadraticCurveTo', args: [200, 100, 200, 106]}, - {name: 'lineTo', args: [200, 194]}, - {name: 'quadraticCurveTo', args: [200, 200, 194, 200]}, - {name: 'lineTo', args: [106, 200]}, - {name: 'quadraticCurveTo', args: [100, 200, 100, 194]}, - {name: 'lineTo', args: [100, 106]}, - {name: 'quadraticCurveTo', args: [100, 100, 106, 100]}, - {name: 'closePath', args: []}, - {name: 'fill', args: []}, - {name: 'stroke', args: []} - ]; - - var mockContext = window.createMockContext(); - var tooltip = new Tooltip({ - _chart: { - options: { - plugins: { - tooltip: { - animation: false, - } - } - } - } - }); - - it('Should go left', function() { - mockContext.resetCalls(); - Chart.helpers.merge(tooltip, makeView('left', 'left', 'left')); - tooltip.draw(mockContext); - - expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ - {name: 'setTextAlign', args: ['left']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['title', 105, 111]}, - {name: 'setTextAlign', args: ['left']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'fillText', args: ['label', 105, 129]}, - {name: 'setTextAlign', args: ['left']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['footer', 105, 147]}, - {name: 'restore', args: []} - ])); - }); - - it('Should go right', function() { - mockContext.resetCalls(); - Chart.helpers.merge(tooltip, makeView('right', 'right', 'right')); - tooltip.draw(mockContext); - - expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ - {name: 'setTextAlign', args: ['right']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['title', 195, 111]}, - {name: 'setTextAlign', args: ['right']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'fillText', args: ['label', 195, 129]}, - {name: 'setTextAlign', args: ['right']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['footer', 195, 147]}, - {name: 'restore', args: []} - ])); - }); - - it('Should center', function() { - mockContext.resetCalls(); - Chart.helpers.merge(tooltip, makeView('center', 'center', 'center')); - tooltip.draw(mockContext); - - expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ - {name: 'setTextAlign', args: ['center']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['title', 150, 111]}, - {name: 'setTextAlign', args: ['center']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'fillText', args: ['label', 150, 129]}, - {name: 'setTextAlign', args: ['center']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['footer', 150, 147]}, - {name: 'restore', args: []} - ])); - }); - - it('Should allow mixed', function() { - mockContext.resetCalls(); - Chart.helpers.merge(tooltip, makeView('right', 'center', 'left')); - tooltip.draw(mockContext); - - expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ - {name: 'setTextAlign', args: ['right']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['title', 195, 111]}, - {name: 'setTextAlign', args: ['center']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'fillText', args: ['label', 150, 129]}, - {name: 'setTextAlign', args: ['left']}, - {name: 'setTextBaseline', args: ['middle']}, - {name: 'setFillStyle', args: ['#fff']}, - {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, - {name: 'fillText', args: ['footer', 105, 147]}, - {name: 'restore', args: []} - ])); - }); - }); - - describe('active events', function() { - it('should set the active events', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - label: 'Dataset 1', - data: [10, 20, 30], - pointHoverBorderColor: 'rgb(255, 0, 0)', - pointHoverBackgroundColor: 'rgb(0, 255, 0)' - }], - labels: ['Point 1', 'Point 2', 'Point 3'] - }, - }); - - const meta = chart.getDatasetMeta(0); - chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 0, y: 0}); - expect(chart.tooltip.getActiveElements()[0].element).toBe(meta.data[0]); - }); - }); + describe('auto', jasmine.fixture.specs('core.tooltip')); + + describe('config', function() { + it('should not include the dataset label in the body string if not defined', function() { + var data = { + datasets: [{ + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }; + var tooltipItem = { + index: 1, + datasetIndex: 0, + dataset: data.datasets[0], + label: 'Point 2', + formattedValue: '20' + }; + + var label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem); + expect(label).toBe('20'); + + data.datasets[0].label = 'My dataset'; + label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem); + expect(label).toBe('My dataset: 20'); + }); + }); + + describe('index mode', function() { + it('Should only use x distance when intersect is false', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + intersect: false, + } + }, + hover: { + mode: 'index', + intersect: false + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip.options.xPadding).toEqual(6); + expect(tooltip.options.yPadding).toEqual(6); + expect(tooltip.xAlign).toEqual('left'); + expect(tooltip.yAlign).toEqual('center'); + expect(tooltip.options.bodyColor).toEqual('#fff'); + + expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: defaults.font.style, + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + bodyAlign: 'left', + bodySpacing: 2, + })); + + expect(tooltip.options.titleColor).toEqual('#fff'); + expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + })); + + expect(tooltip.options.footerColor).toEqual('#fff'); + expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + // Appearance + caretSize: 5, + caretPadding: 2, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + multiKeyBackground: '#fff', + displayColors: true + })); + + expect(tooltip).toEqual(jasmine.objectContaining({ + opacity: 1, + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 1: 20'], + after: [] + }, { + before: [], + lines: ['Dataset 2: 40'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(155); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: chart.chartArea.top + 10}); + }); + + it('Should only display if intersecting if intersect is set', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + intersect: true + } + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + + expect(tooltip).toEqual(jasmine.objectContaining({ + opacity: 0, + })); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: 0}); + }); + }); + + it('Should display in single mode', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'nearest', + intersect: true + } + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip.options.xPadding).toEqual(6); + expect(tooltip.options.yPadding).toEqual(6); + expect(tooltip.xAlign).toEqual('left'); + expect(tooltip.yAlign).toEqual('center'); + + expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: defaults.font.style, + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + bodyAlign: 'left', + bodySpacing: 2, + })); + + expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + })); + + expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + // Appearance + caretSize: 5, + caretPadding: 2, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + multiKeyBackground: '#fff', + displayColors: true + })); + + expect(tooltip.opacity).toEqual(1); + expect(tooltip.title).toEqual(['Point 2']); + expect(tooltip.beforeBody).toEqual([]); + expect(tooltip.body).toEqual([{ + before: [], + lines: ['Dataset 1: 20'], + after: [] + }]); + expect(tooltip.afterBody).toEqual([]); + expect(tooltip.footer).toEqual([]); + expect(tooltip.labelTextColors).toEqual(['#fff']); + + expect(tooltip.labelColors).toEqual([{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }]); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(312); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it('Should display information from user callbacks', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + callbacks: { + beforeTitle: function() { + return 'beforeTitle'; + }, + title: function() { + return 'title'; + }, + afterTitle: function() { + return 'afterTitle'; + }, + beforeBody: function() { + return 'beforeBody'; + }, + beforeLabel: function() { + return 'beforeLabel'; + }, + label: function() { + return 'label'; + }, + afterLabel: function() { + return 'afterLabel'; + }, + afterBody: function() { + return 'afterBody'; + }, + beforeFooter: function() { + return 'beforeFooter'; + }, + footer: function() { + return 'footer'; + }, + afterFooter: function() { + return 'afterFooter'; + }, + labelTextColor: function() { + return 'labelTextColor'; + }, + labelPointStyle: function() { + return { + pointStyle: 'labelPointStyle', + rotation: 42 + }; + } + } + } + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip.options.xPadding).toEqual(6); + expect(tooltip.options.yPadding).toEqual(6); + expect(tooltip.xAlign).toEqual('left'); + expect(tooltip.yAlign).toEqual('center'); + + expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: defaults.font.style, + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + bodyAlign: 'left', + bodySpacing: 2, + })); + + expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + titleSpacing: 2, + titleMarginBottom: 6, + })); + + expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + // Appearance + caretSize: 5, + caretPadding: 2, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + multiKeyBackground: '#fff', + })); + + expect(tooltip).toEqual(jasmine.objectContaining({ + opacity: 1, + + // Text + title: ['beforeTitle', 'title', 'afterTitle'], + beforeBody: ['beforeBody'], + body: [{ + before: ['beforeLabel'], + lines: ['label'], + after: ['afterLabel'] + }, { + before: ['beforeLabel'], + lines: ['label'], + after: ['afterLabel'] + }], + afterBody: ['afterBody'], + footer: ['beforeFooter', 'footer', 'afterFooter'], + labelTextColors: ['labelTextColor', 'labelTextColor'], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }], + labelPointStyles: [{ + pointStyle: 'labelPointStyle', + rotation: 42 + }, { + pointStyle: 'labelPointStyle', + rotation: 42 + }] + })); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(75); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + + it('Should provide context object to user callbacks', function(done) { + const chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [{x: 1, y: 10}, {x: 2, y: 20}, {x: 3, y: 30}] + }] + }, + options: { + scales: { + x: { + type: 'linear' + } + }, + plugins: { + tooltip: { + mode: 'index', + callbacks: { + beforeLabel: function(ctx) { + return ctx.parsed.x + ',' + ctx.parsed.y; + } + } + } + } + } + }); + + // Trigger an event over top of the + const meta = chart.getDatasetMeta(0); + const point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + const tooltip = chart.tooltip; + + expect(tooltip.body[0].before).toEqual(['2,20']); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + it('Should allow sorting items', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + itemSort: function(a, b) { + return a.datasetIndex > b.datasetIndex ? -1 : 1; + } + } + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }, { + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(155); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point0); + + }); + + it('Should allow reversing items', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + reverse: true + } + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }, { + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(155); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point0); + }); + + it('Should follow dataset order', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)', + order: 10 + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)', + order: 5 + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index' + } + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }, { + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + expect(tooltip.x).toBeCloseToPixel(267); + expect(tooltip.y).toBeCloseToPixel(155); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point0); + }); + + it('should filter items from the tooltip using the callback', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)', + tooltipHidden: true + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + filter: function(tooltipItem, index, tooltipItems, data) { + // For testing purposes remove the first dataset that has a tooltipHidden property + return !data.datasets[tooltipItem.datasetIndex].tooltipHidden; + } + } + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point0); + }); + + it('should set the caretPadding based on a config setting', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)', + tooltipHidden: true + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + caretPadding: 10 + } + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + // Positioning + caretPadding: 10, + })); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point0); + }); + + ['line', 'bar'].forEach(function(type) { + it('Should have dataPoints in a ' + type + ' chart', function(done) { + var chart = window.acquireChart({ + type: type, + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'nearest', + intersect: true + } + } + } + }); + + // Trigger an event over top of the element + var pointIndex = 1; + var datasetIndex = 0; + var point = chart.getDatasetMeta(datasetIndex).data[pointIndex]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + + expect(tooltip instanceof Object).toBe(true); + expect(tooltip.dataPoints instanceof Array).toBe(true); + expect(tooltip.dataPoints.length).toBe(1); + + var tooltipItem = tooltip.dataPoints[0]; + + expect(tooltipItem.dataIndex).toBe(pointIndex); + expect(tooltipItem.datasetIndex).toBe(datasetIndex); + expect(typeof tooltipItem.label).toBe('string'); + expect(tooltipItem.label).toBe(chart.data.labels[pointIndex]); + expect(typeof tooltipItem.formattedValue).toBe('string'); + expect(tooltipItem.formattedValue).toBe('' + chart.data.datasets[datasetIndex].data[pointIndex]); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); + + it('Should not update if active element has not changed', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'nearest', + intersect: true, + callbacks: { + title: function() { + return 'registering callback...'; + } + } + } + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var firstPoint = meta.data[1]; + + var tooltip = chart.tooltip; + spyOn(tooltip, 'update').and.callThrough(); + + afterEvent(chart, 'mousemove', function() { + expect(tooltip.update).toHaveBeenCalledWith(true); + + // Reset calls + tooltip.update.calls.reset(); + + afterEvent(chart, 'mousemove', function() { + expect(tooltip.update).not.toHaveBeenCalled(); + + done(); + }); + // Second dispatch change event (same event), should not update tooltip + jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); + }); + // First dispatch change event, should update tooltip + jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); + }); + + it('Should update if active elements are the same, but the position has changed', function(done) { + const chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + scales: { + x: { + stacked: true, + }, + y: { + stacked: true + } + }, + plugins: { + tooltip: { + mode: 'nearest', + position: 'nearest', + intersect: true, + callbacks: { + title: function() { + return 'registering callback...'; + } + } + } + } + } + }); + + // Trigger an event over top of the + const meta = chart.getDatasetMeta(0); + const firstPoint = meta.data[1]; + + const meta2 = chart.getDatasetMeta(1); + const secondPoint = meta2.data[1]; + + const tooltip = chart.tooltip; + spyOn(tooltip, 'update'); + + afterEvent(chart, 'mousemove', function() { + expect(tooltip.update).toHaveBeenCalledWith(true); + + // Reset calls + tooltip.update.calls.reset(); + + afterEvent(chart, 'mousemove', function() { + expect(tooltip.update).toHaveBeenCalledWith(true); + + done(); + }); + // Second dispatch change event (same event), should update tooltip + // because position mode is 'nearest' + jasmine.triggerMouseEvent(chart, 'mousemove', secondPoint); + }); + // First dispatch change event, should update tooltip + jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint); + }); + + describe('positioners', function() { + it('Should call custom positioner with correct parameters and scope', function(done) { + + tooltipPlugin.positioners.test = function() { + return {x: 0, y: 0}; + }; + + spyOn(tooltipPlugin.positioners, 'test').and.callThrough(); + + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'nearest', + position: 'test' + } + } + } + }); + + // Trigger an event over top of the + var pointIndex = 1; + var datasetIndex = 0; + var meta = chart.getDatasetMeta(datasetIndex); + var point = meta.data[pointIndex]; + var fn = tooltipPlugin.positioners.test; + + afterEvent(chart, 'mousemove', function() { + expect(fn.calls.count()).toBe(2); + expect(fn.calls.first().args[0] instanceof Array).toBe(true); + expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'x')).toBe(true); + expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'y')).toBe(true); + expect(fn.calls.first().object instanceof Tooltip).toBe(true); + + done(); + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + }); + + it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', function(done) { + var chart = window.acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [ + 50, + 50 + ], + backgroundColor: [ + 'rgb(255, 0, 0)', + 'rgb(0, 255, 0)' + ], + label: 'Dataset 1' + }], + labels: [ + 'Red long tooltip text to avoid unnecessary loop steps', + 'Green long tooltip text to avoid unnecessary loop steps' + ] + }, + options: { + responsive: true, + animation: { + // without this slice center point is calculated wrong + animateRotate: false + }, + plugins: { + tooltip: { + animation: false + } + } + } + }); + + function testSlice(slice, count) { + var meta = chart.getDatasetMeta(0); + var point = meta.data[slice].getCenterPoint(); + var tooltipPosition = meta.data[slice].tooltipPosition(); + + function recursive(left) { + chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'XX'; + chart.update(); + + afterEvent(chart, 'mouseout', function() { + afterEvent(chart, 'mousemove', function() { + var tooltip = chart.tooltip; + expect(tooltip.dataPoints.length).toBe(1); + expect(tooltip.x).toBeGreaterThanOrEqual(0); + if (tooltip.width <= chart.width) { + expect(tooltip.x + tooltip.width).toBeLessThanOrEqual(chart.width); + } + expect(tooltip.caretX).toBeCloseToPixel(tooltipPosition.x); + // if tooltip is longer than chart area then all tests done + if (tooltip.width > chart.width || left === 0) { + done(left === 0 && new Error('max iterations reached')); + } else { + recursive(left - 1); + } + }); + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + jasmine.triggerMouseEvent(chart, 'mouseout', point); + } + + recursive(count); + } + + // Trigger an event over top of the slice + for (var slice = 0; slice < 2; slice++) { + testSlice(slice, 20); + } + }); + + it('Should split newlines into separate lines in user callbacks', function(done) { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + plugins: { + tooltip: { + mode: 'index', + callbacks: { + beforeTitle: function() { + return 'beforeTitle\nnewline'; + }, + title: function() { + return 'title\nnewline'; + }, + afterTitle: function() { + return 'afterTitle\nnewline'; + }, + beforeBody: function() { + return 'beforeBody\nnewline'; + }, + beforeLabel: function() { + return 'beforeLabel\nnewline'; + }, + label: function() { + return 'label'; + }, + afterLabel: function() { + return 'afterLabel\nnewline'; + }, + afterBody: function() { + return 'afterBody\nnewline'; + }, + beforeFooter: function() { + return 'beforeFooter\nnewline'; + }, + footer: function() { + return 'footer\nnewline'; + }, + afterFooter: function() { + return 'afterFooter\nnewline'; + }, + labelTextColor: function() { + return 'labelTextColor'; + } + } + } + } + } + }); + + // Trigger an event over top of the + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + afterEvent(chart, 'mousemove', function() { + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var defaults = Chart.defaults; + + expect(tooltip.options.xPadding).toEqual(6); + expect(tooltip.options.yPadding).toEqual(6); + expect(tooltip.xAlign).toEqual('center'); + expect(tooltip.yAlign).toEqual('top'); + + expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: defaults.font.style, + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + bodyAlign: 'left', + bodySpacing: 2, + })); + + expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + })); + + expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({ + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + })); + + expect(tooltip.options).toEqual(jasmine.objectContaining({ + // Appearance + caretSize: 5, + caretPadding: 2, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + multiKeyBackground: '#fff', + })); + + expect(tooltip).toEqual(jasmine.objectContaining({ + opacity: 1, + + // Text + title: ['beforeTitle', 'newline', 'title', 'newline', 'afterTitle', 'newline'], + beforeBody: ['beforeBody', 'newline'], + body: [{ + before: ['beforeLabel', 'newline'], + lines: ['label'], + after: ['afterLabel', 'newline'] + }, { + before: ['beforeLabel', 'newline'], + lines: ['label'], + after: ['afterLabel', 'newline'] + }], + afterBody: ['afterBody', 'newline'], + footer: ['beforeFooter', 'newline', 'footer', 'newline', 'afterFooter', 'newline'], + labelTextColors: ['labelTextColor', 'labelTextColor'], + labelColors: [{ + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }, { + borderColor: defaults.borderColor, + backgroundColor: defaults.backgroundColor + }] + })); + + done(); + }); + + jasmine.triggerMouseEvent(chart, 'mousemove', point); + }); + + describe('text align', function() { + var defaults = Chart.defaults; + var makeView = function(title, body, footer) { + return { + // Positioning + x: 100, + y: 100, + width: 100, + height: 100, + xAlign: 'left', + yAlign: 'top', + + options: { + enabled: true, + + xPadding: 5, + yPadding: 5, + + // Body + bodyFont: { + family: defaults.font.family, + style: defaults.font.style, + size: defaults.font.size, + }, + bodyColor: '#fff', + bodyAlign: body, + bodySpacing: 2, + + // Title + titleFont: { + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + }, + titleColor: '#fff', + titleAlign: title, + titleSpacing: 2, + titleMarginBottom: 6, + + // Footer + footerFont: { + family: defaults.font.family, + style: 'bold', + size: defaults.font.size, + }, + footerColor: '#fff', + footerAlign: footer, + footerSpacing: 2, + footerMarginTop: 6, + + // Appearance + caretSize: 5, + cornerRadius: 6, + caretPadding: 2, + borderColor: '#aaa', + borderWidth: 1, + backgroundColor: 'rgba(0,0,0,0.8)', + multiKeyBackground: '#fff', + displayColors: false + + }, + opacity: 1, + + // Text + title: ['title'], + beforeBody: [], + body: [{ + before: [], + lines: ['label'], + after: [] + }], + afterBody: [], + footer: ['footer'], + labelTextColors: ['#fff'], + labelColors: [{ + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgb(0, 255, 0)' + }, { + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgb(0, 255, 255)' + }] + }; + }; + var drawBody = [ + {name: 'save', args: []}, + {name: 'setFillStyle', args: ['rgba(0,0,0,0.8)']}, + {name: 'setStrokeStyle', args: ['#aaa']}, + {name: 'setLineWidth', args: [1]}, + {name: 'beginPath', args: []}, + {name: 'moveTo', args: [106, 100]}, + {name: 'lineTo', args: [106, 100]}, + {name: 'lineTo', args: [111, 95]}, + {name: 'lineTo', args: [116, 100]}, + {name: 'lineTo', args: [194, 100]}, + {name: 'quadraticCurveTo', args: [200, 100, 200, 106]}, + {name: 'lineTo', args: [200, 194]}, + {name: 'quadraticCurveTo', args: [200, 200, 194, 200]}, + {name: 'lineTo', args: [106, 200]}, + {name: 'quadraticCurveTo', args: [100, 200, 100, 194]}, + {name: 'lineTo', args: [100, 106]}, + {name: 'quadraticCurveTo', args: [100, 100, 106, 100]}, + {name: 'closePath', args: []}, + {name: 'fill', args: []}, + {name: 'stroke', args: []} + ]; + + var mockContext = window.createMockContext(); + var tooltip = new Tooltip({ + _chart: { + options: { + plugins: { + tooltip: { + animation: false, + } + } + } + } + }); + + it('Should go left', function() { + mockContext.resetCalls(); + Chart.helpers.merge(tooltip, makeView('left', 'left', 'left')); + tooltip.draw(mockContext); + + expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ + {name: 'setTextAlign', args: ['left']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['title', 105, 111]}, + {name: 'setTextAlign', args: ['left']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'fillText', args: ['label', 105, 129]}, + {name: 'setTextAlign', args: ['left']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['footer', 105, 147]}, + {name: 'restore', args: []} + ])); + }); + + it('Should go right', function() { + mockContext.resetCalls(); + Chart.helpers.merge(tooltip, makeView('right', 'right', 'right')); + tooltip.draw(mockContext); + + expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ + {name: 'setTextAlign', args: ['right']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['title', 195, 111]}, + {name: 'setTextAlign', args: ['right']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'fillText', args: ['label', 195, 129]}, + {name: 'setTextAlign', args: ['right']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['footer', 195, 147]}, + {name: 'restore', args: []} + ])); + }); + + it('Should center', function() { + mockContext.resetCalls(); + Chart.helpers.merge(tooltip, makeView('center', 'center', 'center')); + tooltip.draw(mockContext); + + expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ + {name: 'setTextAlign', args: ['center']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['title', 150, 111]}, + {name: 'setTextAlign', args: ['center']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'fillText', args: ['label', 150, 129]}, + {name: 'setTextAlign', args: ['center']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['footer', 150, 147]}, + {name: 'restore', args: []} + ])); + }); + + it('Should allow mixed', function() { + mockContext.resetCalls(); + Chart.helpers.merge(tooltip, makeView('right', 'center', 'left')); + tooltip.draw(mockContext); + + expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [ + {name: 'setTextAlign', args: ['right']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['title', 195, 111]}, + {name: 'setTextAlign', args: ['center']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFont', args: ["normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'fillText', args: ['label', 150, 129]}, + {name: 'setTextAlign', args: ['left']}, + {name: 'setTextBaseline', args: ['middle']}, + {name: 'setFillStyle', args: ['#fff']}, + {name: 'setFont', args: ["bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"]}, + {name: 'fillText', args: ['footer', 105, 147]}, + {name: 'restore', args: []} + ])); + }); + }); + + describe('active events', function() { + it('should set the active events', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + }); + + const meta = chart.getDatasetMeta(0); + chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 0, y: 0}); + expect(chart.tooltip.getActiveElements()[0].element).toBe(meta.data[0]); + }); + }); }); diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 884627af6ab..5fc3872e1a2 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -1,536 +1,536 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Category scale tests', function() { - describe('auto', jasmine.fixture.specs('scale.category')); - - it('Should register the constructor with the registry', function() { - var Constructor = Chart.registry.getScale('category'); - expect(Constructor).not.toBe(undefined); - expect(typeof Constructor).toBe('function'); - }); - - it('Should have the correct default config', function() { - var defaultConfig = Chart.defaults.scales.category; - expect(defaultConfig).toEqual({ - ticks: { - callback: Chart.registry.getScale('category').prototype.getLabelForValue - } - }); - }); - - - it('Should generate ticks from the data xLabels', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - data: { - xLabels: labels, - datasets: [{ - data: [10, 5, 0, 25, 78] - }] - }, - options: { - scales: { - x: { - type: 'category', - } - } - } - }); - - var scale = chart.scales.x; - expect(getLabels(scale)).toEqual(labels); - }); - - it('Should generate ticks from the data yLabels', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - data: { - yLabels: labels, - datasets: [{ - data: [10, 5, 0, 25, 78] - }] - }, - options: { - scales: { - y: { - type: 'category' - } - } - } - }); - - var scale = chart.scales.y; - expect(getLabels(scale)).toEqual(labels); - }); - - it('Should generate ticks from the axis labels', function() { - var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }] - }, - options: { - scales: { - x: { - type: 'category', - labels: labels - } - } - } - }); - - var scale = chart.scales.x; - expect(getLabels(scale)).toEqual(labels); - }); - - it('Should generate missing labels', function() { - var labels = ['a', 'b', 'c', 'd']; - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: {a: 1, b: 3, c: -1, d: 10} - }] - }, - options: { - scales: { - x: { - type: 'category', - labels: ['a'] - } - } - } - }); - - var scale = chart.scales.x; - expect(getLabels(scale)).toEqual(labels); - - }); - - it('should get the correct label for the index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var scale = chart.scales.x; - - expect(scale.getLabelForValue(1)).toBe('tick2'); - }); - - it('Should get the correct pixel for a value when horizontal', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue(0)).toBeCloseToPixel(23 + 6); // plus lineHeight - expect(xScale.getValueForPixel(23)).toBe(0); - - expect(xScale.getPixelForValue(4)).toBeCloseToPixel(487); - expect(xScale.getValueForPixel(487)).toBe(4); - - xScale.options.offset = true; - chart.update(); - - expect(xScale.getPixelForValue(0)).toBeCloseToPixel(71 + 6); // plus lineHeight - expect(xScale.getValueForPixel(69)).toBe(0); - - expect(xScale.getPixelForValue(4)).toBeCloseToPixel(461); - expect(xScale.getValueForPixel(417)).toBe(4); - }); - - it('Should get the correct pixel for a value when there are repeated labels', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue('tick1')).toBeCloseToPixel(23 + 6); // plus lineHeight - }); - - it('Should get the correct pixel for a value when horizontal and zoomed', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom', - min: 'tick2', - max: 'tick4' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue(1)).toBeCloseToPixel(23 + 6); // plus lineHeight - expect(xScale.getPixelForValue(3)).toBeCloseToPixel(496); - - xScale.options.offset = true; - chart.update(); - - expect(xScale.getPixelForValue(1)).toBeCloseToPixel(103 + 6); // plus lineHeight - expect(xScale.getPixelForValue(3)).toBeCloseToPixel(429); - }); - - it('should get the correct pixel for a value when vertical', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: ['3', '5', '1', '4', '2'] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], - yLabels: ['1', '2', '3', '4', '5'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom', - }, - y: { - type: 'category', - position: 'left' - } - } - } - }); - - var yScale = chart.scales.y; - expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32); - expect(yScale.getValueForPixel(257)).toBe(2); - - expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481); - expect(yScale.getValueForPixel(144)).toBe(1); - - yScale.options.offset = true; - chart.update(); - - expect(yScale.getPixelForValue(0)).toBeCloseToPixel(77); - expect(yScale.getValueForPixel(256)).toBe(2); - - expect(yScale.getPixelForValue(4)).toBeCloseToPixel(436); - expect(yScale.getValueForPixel(167)).toBe(1); - }); - - it('should get the correct pixel for a value when vertical and zoomed', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: ['3', '5', '1', '4', '2'] - }], - labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], - yLabels: ['1', '2', '3', '4', '5'] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom', - }, - y: { - type: 'category', - position: 'left', - min: '2', - max: '4' - } - } - } - }); - - var yScale = chart.scales.y; - - expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); - expect(yScale.getPixelForValue(3)).toBeCloseToPixel(482); - - yScale.options.offset = true; - chart.update(); - - expect(yScale.getPixelForValue(1)).toBeCloseToPixel(107); - expect(yScale.getPixelForValue(3)).toBeCloseToPixel(407); - }); - - it('Should get the correct pixel for an object value when horizontal', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [ - {x: 0, y: 10}, - {x: 1, y: 5}, - {x: 2, y: 0}, - {x: 3, y: 25}, - {x: 0, y: 78} - ] - }], - labels: [0, 1, 2, 3] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue(0)).toBeCloseToPixel(29); - expect(xScale.getPixelForValue(3)).toBeCloseToPixel(506); - expect(xScale.getPixelForValue(4)).toBeCloseToPixel(664); - }); - - it('Should get the correct pixel for an object value when vertical', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [ - {x: 0, y: 2}, - {x: 1, y: 4}, - {x: 2, y: 0}, - {x: 3, y: 3}, - {x: 0, y: 1} - ] - }], - labels: [0, 1, 2, 3], - yLabels: [0, 1, 2, 3, 4] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'category', - position: 'left' - } - } - } - }); - - var yScale = chart.scales.y; - expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32); - expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481); - }); - - it('Should get the correct pixel for an object value in a bar chart', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [ - {x: 0, y: 10}, - {x: 1, y: 5}, - {x: 2, y: 0}, - {x: 3, y: 25}, - {x: 0, y: 78} - ] - }], - labels: [0, 1, 2, 3] - }, - options: { - scales: { - x: { - type: 'category', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89); - expect(xScale.getPixelForValue(3)).toBeCloseToPixel(449); - expect(xScale.getPixelForValue(4)).toBeCloseToPixel(569); - }); - - it('Should get the correct pixel for an object value in a horizontal bar chart', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [ - {x: 10, y: 0}, - {x: 5, y: 1}, - {x: 0, y: 2}, - {x: 25, y: 3}, - {x: 78, y: 0} - ] - }], - labels: [0, 1, 2, 3] - }, - options: { - indexAxis: 'y', - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'category' - } - } - } - }); - - var yScale = chart.scales.y; - expect(yScale.getPixelForValue(0)).toBeCloseToPixel(88); - expect(yScale.getPixelForValue(3)).toBeCloseToPixel(426); - expect(yScale.getPixelForValue(4)).toBeCloseToPixel(538); - }); - - it('Should be consistent on pixels and values with autoSkipped ticks', function() { - var labels = []; - for (let i = 0; i < 50; i++) { - labels.push('very long label ' + i); - } - var chart = window.acquireChart({ - type: 'bar', - data: { - labels, - datasets: [{ - data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - }] - } - }); - - var scale = chart.scales.x; - expect(scale.ticks.length).toBeLessThan(50); - - let x = 0; - for (let i = 0; i < 50; i++) { - var x2 = scale.getPixelForValue(labels[i]); - var x3 = scale.getPixelForValue(i); - expect(x2).toEqual(x3); - expect(x2).toBeGreaterThan(x); - expect(scale.getValueForPixel(x2)).toBe(i); - x = x2; - } - }); - - it('Should bound to ticks/data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['a', 'b', 'c', 'd'], - datasets: [{ - data: {b: 1, c: 99} - }] - }, - options: { - scales: { - x: { - type: 'category', - bounds: 'data' - } - } - } - }); - - expect(chart.scales.x.min).toEqual(1); - expect(chart.scales.x.max).toEqual(2); - - chart.options.scales.x.bounds = 'ticks'; - chart.update(); - - expect(chart.scales.x.min).toEqual(0); - expect(chart.scales.x.max).toEqual(3); - }); + describe('auto', jasmine.fixture.specs('scale.category')); + + it('Should register the constructor with the registry', function() { + var Constructor = Chart.registry.getScale('category'); + expect(Constructor).not.toBe(undefined); + expect(typeof Constructor).toBe('function'); + }); + + it('Should have the correct default config', function() { + var defaultConfig = Chart.defaults.scales.category; + expect(defaultConfig).toEqual({ + ticks: { + callback: Chart.registry.getScale('category').prototype.getLabelForValue + } + }); + }); + + + it('Should generate ticks from the data xLabels', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + data: { + xLabels: labels, + datasets: [{ + data: [10, 5, 0, 25, 78] + }] + }, + options: { + scales: { + x: { + type: 'category', + } + } + } + }); + + var scale = chart.scales.x; + expect(getLabels(scale)).toEqual(labels); + }); + + it('Should generate ticks from the data yLabels', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + data: { + yLabels: labels, + datasets: [{ + data: [10, 5, 0, 25, 78] + }] + }, + options: { + scales: { + y: { + type: 'category' + } + } + } + }); + + var scale = chart.scales.y; + expect(getLabels(scale)).toEqual(labels); + }); + + it('Should generate ticks from the axis labels', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }] + }, + options: { + scales: { + x: { + type: 'category', + labels: labels + } + } + } + }); + + var scale = chart.scales.x; + expect(getLabels(scale)).toEqual(labels); + }); + + it('Should generate missing labels', function() { + var labels = ['a', 'b', 'c', 'd']; + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: {a: 1, b: 3, c: -1, d: 10} + }] + }, + options: { + scales: { + x: { + type: 'category', + labels: ['a'] + } + } + } + }); + + var scale = chart.scales.x; + expect(getLabels(scale)).toEqual(labels); + + }); + + it('should get the correct label for the index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var scale = chart.scales.x; + + expect(scale.getLabelForValue(1)).toBe('tick2'); + }); + + it('Should get the correct pixel for a value when horizontal', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue(0)).toBeCloseToPixel(23 + 6); // plus lineHeight + expect(xScale.getValueForPixel(23)).toBe(0); + + expect(xScale.getPixelForValue(4)).toBeCloseToPixel(487); + expect(xScale.getValueForPixel(487)).toBe(4); + + xScale.options.offset = true; + chart.update(); + + expect(xScale.getPixelForValue(0)).toBeCloseToPixel(71 + 6); // plus lineHeight + expect(xScale.getValueForPixel(69)).toBe(0); + + expect(xScale.getPixelForValue(4)).toBeCloseToPixel(461); + expect(xScale.getValueForPixel(417)).toBe(4); + }); + + it('Should get the correct pixel for a value when there are repeated labels', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue('tick1')).toBeCloseToPixel(23 + 6); // plus lineHeight + }); + + it('Should get the correct pixel for a value when horizontal and zoomed', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom', + min: 'tick2', + max: 'tick4' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue(1)).toBeCloseToPixel(23 + 6); // plus lineHeight + expect(xScale.getPixelForValue(3)).toBeCloseToPixel(496); + + xScale.options.offset = true; + chart.update(); + + expect(xScale.getPixelForValue(1)).toBeCloseToPixel(103 + 6); // plus lineHeight + expect(xScale.getPixelForValue(3)).toBeCloseToPixel(429); + }); + + it('should get the correct pixel for a value when vertical', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: ['3', '5', '1', '4', '2'] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + yLabels: ['1', '2', '3', '4', '5'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom', + }, + y: { + type: 'category', + position: 'left' + } + } + } + }); + + var yScale = chart.scales.y; + expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32); + expect(yScale.getValueForPixel(257)).toBe(2); + + expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481); + expect(yScale.getValueForPixel(144)).toBe(1); + + yScale.options.offset = true; + chart.update(); + + expect(yScale.getPixelForValue(0)).toBeCloseToPixel(77); + expect(yScale.getValueForPixel(256)).toBe(2); + + expect(yScale.getPixelForValue(4)).toBeCloseToPixel(436); + expect(yScale.getValueForPixel(167)).toBe(1); + }); + + it('should get the correct pixel for a value when vertical and zoomed', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: ['3', '5', '1', '4', '2'] + }], + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + yLabels: ['1', '2', '3', '4', '5'] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom', + }, + y: { + type: 'category', + position: 'left', + min: '2', + max: '4' + } + } + } + }); + + var yScale = chart.scales.y; + + expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); + expect(yScale.getPixelForValue(3)).toBeCloseToPixel(482); + + yScale.options.offset = true; + chart.update(); + + expect(yScale.getPixelForValue(1)).toBeCloseToPixel(107); + expect(yScale.getPixelForValue(3)).toBeCloseToPixel(407); + }); + + it('Should get the correct pixel for an object value when horizontal', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [ + {x: 0, y: 10}, + {x: 1, y: 5}, + {x: 2, y: 0}, + {x: 3, y: 25}, + {x: 0, y: 78} + ] + }], + labels: [0, 1, 2, 3] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue(0)).toBeCloseToPixel(29); + expect(xScale.getPixelForValue(3)).toBeCloseToPixel(506); + expect(xScale.getPixelForValue(4)).toBeCloseToPixel(664); + }); + + it('Should get the correct pixel for an object value when vertical', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [ + {x: 0, y: 2}, + {x: 1, y: 4}, + {x: 2, y: 0}, + {x: 3, y: 3}, + {x: 0, y: 1} + ] + }], + labels: [0, 1, 2, 3], + yLabels: [0, 1, 2, 3, 4] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'category', + position: 'left' + } + } + } + }); + + var yScale = chart.scales.y; + expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32); + expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481); + }); + + it('Should get the correct pixel for an object value in a bar chart', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [ + {x: 0, y: 10}, + {x: 1, y: 5}, + {x: 2, y: 0}, + {x: 3, y: 25}, + {x: 0, y: 78} + ] + }], + labels: [0, 1, 2, 3] + }, + options: { + scales: { + x: { + type: 'category', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89); + expect(xScale.getPixelForValue(3)).toBeCloseToPixel(449); + expect(xScale.getPixelForValue(4)).toBeCloseToPixel(569); + }); + + it('Should get the correct pixel for an object value in a horizontal bar chart', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [ + {x: 10, y: 0}, + {x: 5, y: 1}, + {x: 0, y: 2}, + {x: 25, y: 3}, + {x: 78, y: 0} + ] + }], + labels: [0, 1, 2, 3] + }, + options: { + indexAxis: 'y', + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'category' + } + } + } + }); + + var yScale = chart.scales.y; + expect(yScale.getPixelForValue(0)).toBeCloseToPixel(88); + expect(yScale.getPixelForValue(3)).toBeCloseToPixel(426); + expect(yScale.getPixelForValue(4)).toBeCloseToPixel(538); + }); + + it('Should be consistent on pixels and values with autoSkipped ticks', function() { + var labels = []; + for (let i = 0; i < 50; i++) { + labels.push('very long label ' + i); + } + var chart = window.acquireChart({ + type: 'bar', + data: { + labels, + datasets: [{ + data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }] + } + }); + + var scale = chart.scales.x; + expect(scale.ticks.length).toBeLessThan(50); + + let x = 0; + for (let i = 0; i < 50; i++) { + var x2 = scale.getPixelForValue(labels[i]); + var x3 = scale.getPixelForValue(i); + expect(x2).toEqual(x3); + expect(x2).toBeGreaterThan(x); + expect(scale.getValueForPixel(x2)).toBe(i); + x = x2; + } + }); + + it('Should bound to ticks/data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['a', 'b', 'c', 'd'], + datasets: [{ + data: {b: 1, c: 99} + }] + }, + options: { + scales: { + x: { + type: 'category', + bounds: 'data' + } + } + } + }); + + expect(chart.scales.x.min).toEqual(1); + expect(chart.scales.x.max).toEqual(2); + + chart.options.scales.x.bounds = 'ticks'; + chart.update(); + + expect(chart.scales.x.min).toEqual(0); + expect(chart.scales.x.max).toEqual(3); + }); }); diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index e7b5d055d87..36bdf225827 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -1,1183 +1,1183 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Linear Scale', function() { - describe('auto', jasmine.fixture.specs('scale.linear')); - - it('Should register the constructor with the registry', function() { - var Constructor = Chart.registry.getScale('linear'); - expect(Constructor).not.toBe(undefined); - expect(typeof Constructor).toBe('function'); - }); - - it('Should have the correct default config', function() { - var defaultConfig = Chart.defaults.scales.linear; - - expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); - }); - - it('Should correctly determine the max & min data values', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, -5, 78, -100] - }, { - yAxisID: 'y2', - data: [-1000, 1000], - }, { - yAxisID: 'y', - data: [150] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear' - }, - y2: { - type: 'linear', - position: 'right', - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-100); - expect(chart.scales.y.max).toBe(150); - }); - - it('Should correctly determine the max & min of string data values', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: ['10', '5', '0', '-5', '78', '-100'] - }, { - yAxisID: 'y2', - data: ['-1000', '1000'], - }, { - yAxisID: 'y', - data: ['150'] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear' - }, - y2: { - type: 'linear', - position: 'right' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-100); - expect(chart.scales.y.max).toBe(150); - }); - - it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - suggestedMin: -10, - suggestedMax: 15 - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-10); - expect(chart.scales.y.max).toBe(15); - }); - - it('Should correctly determine the max & min when no datasets are associated and suggested minimum and maximum are set', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [] - }, - options: { - scales: { - y: { - type: 'linear', - suggestedMin: -10, - suggestedMax: 0 - } - } - } - }); - - expect(chart.scales.y.min).toBe(-10); - expect(chart.scales.y.max).toBe(0); - }); - - it('Should correctly determine the max & min data values ignoring hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: ['10', '5', '0', '-5', '78', '-100'] - }, { - yAxisID: 'y2', - data: ['-1000', '1000'], - }, { - yAxisID: 'y', - data: ['150'], - hidden: true - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear' - }, - y2: { - position: 'right', - type: 'linear' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-100); - expect(chart.scales.y.max).toBe(80); - }); - - it('Should correctly determine the max & min data values ignoring data that is NaN', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [null, 90, NaN, undefined, 45, 30, Infinity, -Infinity] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] - }, - options: { - scales: { - y: { - type: 'linear', - beginAtZero: false - } - } - } - }); - - expect(chart.scales.y.min).toBe(30); - expect(chart.scales.y.max).toBe(90); - - // Scale is now stacked - chart.scales.y.options.stacked = true; - chart.update(); - - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(90); - }); - - it('Should correctly determine the max & min data values for small numbers', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [-1e-8, 3e-8, -4e-8, 6e-8] - }], - labels: ['a', 'b', 'c', 'd'] - }, - options: { - scales: { - y: { - type: 'linear' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min * 1e8).toBeCloseTo(-4); - expect(chart.scales.y.max * 1e8).toBeCloseTo(6); - }); - - it('Should correctly determine the max & min for scatter data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [{ - x: 10, - y: 100 - }, { - x: -10, - y: 0 - }, { - x: 0, - y: 0 - }, { - x: 99, - y: 7 - }] - }], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - chart.update(); - - expect(chart.scales.x.min).toBe(-20); - expect(chart.scales.x.max).toBe(100); - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(100); - }); - - it('Should correctly get the label for the given index', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [{ - x: 10, - y: 100 - }, { - x: -10, - y: 0 - }, { - x: 0, - y: 0 - }, { - x: 99, - y: 7 - }] - }], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - chart.update(); - - expect(chart.scales.y.getLabelForValue(7)).toBe('7'); - }); - - it('Should correctly determine the min and max data values when stacked mode is turned on', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, -5, 78, -100], - type: 'bar' - }, { - yAxisID: 'y2', - data: [-1000, 1000], - }, { - yAxisID: 'y', - data: [150, 0, 0, -100, -10, 9], - type: 'bar' - }, { - yAxisID: 'y', - data: [10, 10, 10, 10, 10, 10], - type: 'line' - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - stacked: true - }, - y2: { - position: 'right', - type: 'linear' - } - } - } - }); - chart.update(); - - expect(chart.scales.y.min).toBe(-150); - expect(chart.scales.y.max).toBe(200); - }); - - it('Should correctly determine the min and max data values when stacked mode is turned on and there are hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, -5, 78, -100], - }, { - yAxisID: 'y2', - data: [-1000, 1000], - }, { - yAxisID: 'y', - data: [150, 0, 0, -100, -10, 9], - }, { - yAxisID: 'y', - data: [10, 20, 30, 40, 50, 60], - hidden: true - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - stacked: true - }, - y2: { - position: 'right', - type: 'linear' - } - } - } - }); - chart.update(); - - expect(chart.scales.y.min).toBe(-150); - expect(chart.scales.y.max).toBe(200); - }); - - it('Should correctly determine the min and max data values when stacked mode is turned on there are multiple types of datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - type: 'bar', - data: [10, 5, 0, -5, 78, -100] - }, { - type: 'line', - data: [10, 10, 10, 10, 10, 10], - }, { - type: 'bar', - data: [150, 0, 0, -100, -10, 9] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - stacked: true - } - } - } - }); - - chart.scales.y.determineDataLimits(); - expect(chart.scales.y.min).toBe(-105); - expect(chart.scales.y.max).toBe(160); - }); - - it('Should ensure that the scale has a max and min that are not equal', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(1); - }); - - it('Should ensure that the scale has a max and min that are not equal when beginAtZero is set', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - beginAtZero: true - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(1); - }); - - it('Should use the suggestedMin and suggestedMax options', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [1, 1, 1, 2, 1, 0] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - suggestedMax: 10, - suggestedMin: -10 - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-10); - expect(chart.scales.y.max).toBe(10); - }); - - it('Should use the min and max options', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [1, 1, 1, 2, 1, 0] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - max: 1010, - min: -1010 - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(-1010); - expect(chart.scales.y.max).toBe(1010); - var labels = getLabels(chart.scales.y); - expect(labels[0]).toBe('-1,010'); - expect(labels[labels.length - 1]).toBe('1,010'); - }); - - it('Should use min, max and stepSize to create fixed spaced ticks', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 3, 6, 8, 3, 1] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - min: 1, - max: 11, - ticks: { - stepSize: 2 - } - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(1); - expect(chart.scales.y.max).toBe(11); - expect(getLabels(chart.scales.y)).toEqual(['1', '3', '5', '7', '9', '11']); - }); - - it('Should create decimal steps if stepSize is a decimal number', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 3, 6, 8, 3, 1] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - ticks: { - stepSize: 2.5 - } - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(10); - expect(getLabels(chart.scales.y)).toEqual(['0', '2.5', '5', '7.5', '10']); - }); - - describe('precision', function() { - it('Should create integer steps if precision is 0', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [0, 1, 2, 1, 0, 1] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - ticks: { - precision: 0 - } - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(2); - expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2']); - }); - - it('Should round the step size to the given number of decimal places', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [0, 0.001, 0.002, 0.003, 0, 0.001] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'linear', - ticks: { - precision: 2 - } - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(0); - expect(chart.scales.y.max).toBe(0.01); - expect(getLabels(chart.scales.y)).toEqual(['0', '0.01']); - }); - }); - - - it('should forcibly include 0 in the range if the beginAtZero option is used', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [20, 30, 40, 50] - }], - labels: ['a', 'b', 'c', 'd'] - }, - options: { - scales: { - y: { - type: 'linear', - beginAtZero: false - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(getLabels(chart.scales.y)).toEqual(['20', '25', '30', '35', '40', '45', '50']); - - chart.scales.y.options.beginAtZero = true; - chart.update(); - expect(getLabels(chart.scales.y)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']); - - chart.data.datasets[0].data = [-20, -30, -40, -50]; - chart.update(); - expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); - - chart.scales.y.options.beginAtZero = false; - chart.update(); - expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']); - }); - - it('Should generate tick marks in the correct order in reversed mode', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['a', 'b', 'c', 'd'] - }, - options: { - scales: { - y: { - type: 'linear', - reverse: true - } - } - } - }); - - expect(getLabels(chart.scales.y)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']); - expect(chart.scales.y.start).toBe(80); - expect(chart.scales.y.end).toBe(0); - }); - - it('should use the correct number of decimal places in the default format function', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [0.06, 0.005, 0, 0.025, 0.0078] - }], - labels: ['a', 'b', 'c', 'd'] - }, - options: { - scales: { - y: { - type: 'linear', - } - } - } - }); - expect(getLabels(chart.scales.y)).toEqual(['0', '0.01', '0.02', '0.03', '0.04', '0.05', '0.06']); - }); - - it('Should correctly limit the maximum number of ticks', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - labels: ['a', 'b'], - datasets: [{ - data: [0.5, 2.5] - }] - }, - options: { - scales: { - y: { - beginAtZero: false - } - } - } - }); - - expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.y.ticks.maxTicksLimit = 11; - chart.update(); - - expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.y.ticks.maxTicksLimit = 21; - chart.update(); - - expect(getLabels(chart.scales.y)).toEqual([ - '0.5', - '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5', - '1.6', '1.7', '1.8', '1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5' - ]); - - chart.options.scales.y.ticks.maxTicksLimit = 11; - chart.options.scales.y.ticks.stepSize = 0.01; - chart.update(); - - expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.y.min = 0.3; - chart.options.scales.y.max = 2.8; - chart.update(); - - expect(getLabels(chart.scales.y)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']); - }); - - it('Should bound to data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['a', 'b'], - datasets: [{ - data: [1, 99] - }] - }, - options: { - scales: { - y: { - bounds: 'data' - } - } - } - }); - - expect(chart.scales.y.min).toEqual(1); - expect(chart.scales.y.max).toEqual(99); - }); - - it('Should build labels using the user supplied callback', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 0, 25, 78] - }], - labels: ['a', 'b', 'c', 'd'] - }, - options: { - scales: { - y: { - type: 'linear', - ticks: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - // Just the index - expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); - }); - - it('Should get the correct pixel value for a point', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: [-1, 1], - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [-1, 1] - }], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - expect(xScale.getPixelForValue(1)).toBeCloseToPixel(501); // right - paddingRight - expect(xScale.getPixelForValue(-1)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace - expect(xScale.getPixelForValue(0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/ - - expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2); - expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2); - expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2); - - var yScale = chart.scales.y; - expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); // right - paddingRight - expect(yScale.getPixelForValue(-1)).toBeCloseToPixel(484); // left + paddingLeft - expect(yScale.getPixelForValue(0)).toBeCloseToPixel(258); // halfway*/ - - expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2); - expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2); - expect(yScale.getValueForPixel(258)).toBeCloseTo(0, 1e-2); - }); - - it('should fit correctly', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [{ - x: 10, - y: 100 - }, { - x: -10, - y: 0 - }, { - x: 0, - y: 0 - }, { - x: 99, - y: 7 - }] - }], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'linear' - } - } - } - }); - - var xScale = chart.scales.x; - var yScale = chart.scales.y; - expect(xScale.paddingTop).toBeCloseToPixel(0); - expect(xScale.paddingBottom).toBeCloseToPixel(0); - expect(xScale.paddingLeft).toBeCloseToPixel(12); - expect(xScale.paddingRight).toBeCloseToPixel(13.5); - expect(xScale.width).toBeCloseToPixel(468 - 6); // minus lineSpace - expect(xScale.height).toBeCloseToPixel(30); - - expect(yScale.paddingTop).toBeCloseToPixel(7); - expect(yScale.paddingBottom).toBeCloseToPixel(7); - expect(yScale.paddingLeft).toBeCloseToPixel(0); - expect(yScale.paddingRight).toBeCloseToPixel(0); - expect(yScale.width).toBeCloseToPixel(30 + 6); // plus lineSpace - expect(yScale.height).toBeCloseToPixel(450); - - // Extra size when scale label showing - xScale.options.scaleLabel.display = true; - yScale.options.scaleLabel.display = true; - chart.update(); - - expect(xScale.paddingTop).toBeCloseToPixel(0); - expect(xScale.paddingBottom).toBeCloseToPixel(0); - expect(xScale.paddingLeft).toBeCloseToPixel(12); - expect(xScale.paddingRight).toBeCloseToPixel(13.5); - expect(xScale.width).toBeCloseToPixel(440); - expect(xScale.height).toBeCloseToPixel(53); - - expect(yScale.paddingTop).toBeCloseToPixel(7); - expect(yScale.paddingBottom).toBeCloseToPixel(7); - expect(yScale.paddingLeft).toBeCloseToPixel(0); - expect(yScale.paddingRight).toBeCloseToPixel(0); - expect(yScale.width).toBeCloseToPixel(58); - expect(yScale.height).toBeCloseToPixel(427); - }); - - it('should fit correctly when display is turned off', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - yAxisID: 'y', - data: [{ - x: 10, - y: 100 - }, { - x: -10, - y: 0 - }, { - x: 0, - y: 0 - }, { - x: 99, - y: 7 - }] - }], - }, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - }, - y: { - type: 'linear', - gridLines: { - drawTicks: false, - drawBorder: false - }, - scaleLabel: { - display: false, - lineHeight: 1.2 - }, - ticks: { - display: false, - padding: 0 - } - } - } - } - }); - - var yScale = chart.scales.y; - expect(yScale.width).toBeCloseToPixel(0); - }); - - it('max and min value should be valid and finite when charts datasets are hidden', function() { - var barData = { - labels: ['S1', 'S2', 'S3'], - datasets: [{ - label: 'Closed', - backgroundColor: '#382765', - data: [2500, 2000, 1500] - }, { - label: 'In Progress', - backgroundColor: '#7BC225', - data: [1000, 2000, 1500] - }, { - label: 'Assigned', - backgroundColor: '#ffC225', - data: [1000, 2000, 1500] - }] - }; - - var chart = window.acquireChart({ - type: 'bar', - data: barData, - options: { - indexAxis: 'y', - scales: { - x: { - stacked: true - }, - y: { - stacked: true - } - } - } - }); - - barData.datasets.forEach(function(data, index) { - var meta = chart.getDatasetMeta(index); - meta.hidden = true; - chart.update(); - }); - - expect(chart.scales.x.min).toEqual(0); - expect(chart.scales.x.max).toEqual(1); - }); - - it('max and min value should be valid when min is set and all datasets are hidden', function() { - var barData = { - labels: ['S1', 'S2', 'S3'], - datasets: [{ - label: 'dataset 1', - backgroundColor: '#382765', - data: [2500, 2000, 1500], - hidden: true, - }] - }; - - var chart = window.acquireChart({ - type: 'bar', - data: barData, - options: { - indexAxis: 'y', - scales: { - x: { - min: 20 - } - } - } - }); - - expect(chart.scales.x.min).toEqual(20); - expect(chart.scales.x.max).toEqual(21); - }); - - it('min settings should be used if set to zero', function() { - var barData = { - labels: ['S1', 'S2', 'S3'], - datasets: [{ - label: 'dataset 1', - backgroundColor: '#382765', - data: [2500, 2000, 1500] - }] - }; - - var chart = window.acquireChart({ - type: 'bar', - data: barData, - options: { - indexAxis: 'y', - scales: { - x: { - min: 0, - max: 3000 - } - } - } - }); - - expect(chart.scales.x.min).toEqual(0); - }); - - it('max settings should be used if set to zero', function() { - var barData = { - labels: ['S1', 'S2', 'S3'], - datasets: [{ - label: 'dataset 1', - backgroundColor: '#382765', - data: [-2500, -2000, -1500] - }] - }; - - var chart = window.acquireChart({ - type: 'bar', - data: barData, - options: { - indexAxis: 'y', - scales: { - x: { - min: -3000, - max: 0 - } - } - } - }); - - expect(chart.scales.x.max).toEqual(0); - }); - - it('Should get correct pixel values when horizontal', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [0.05, -25, 10, 15, 20, 25, 30, 35] - }] - }, - options: { - indexAxis: 'y', - scales: { - x: { - type: 'linear', - } - } - } - }); - - var start = chart.chartArea.left; - var end = chart.chartArea.right; - var min = -30; - var max = 40; - var scale = chart.scales.x; - - expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); - expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); - expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); - expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); - - scale.options.reverse = true; - chart.update(); - - start = chart.chartArea.left; - end = chart.chartArea.right; - - expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); - expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); - expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); - }); - - it('Should get correct pixel values when vertical', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [0.05, -25, 10, 15, 20, 25, 30, 35] - }] - }, - options: { - scales: { - y: { - type: 'linear', - } - } - } - }); - - var start = chart.chartArea.bottom; - var end = chart.chartArea.top; - var min = -30; - var max = 40; - var scale = chart.scales.y; - - expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); - expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); - expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); - expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); - - scale.options.reverse = true; - chart.update(); - - start = chart.chartArea.bottom; - end = chart.chartArea.top; - - expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); - expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); - expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); - }); + describe('auto', jasmine.fixture.specs('scale.linear')); + + it('Should register the constructor with the registry', function() { + var Constructor = Chart.registry.getScale('linear'); + expect(Constructor).not.toBe(undefined); + expect(typeof Constructor).toBe('function'); + }); + + it('Should have the correct default config', function() { + var defaultConfig = Chart.defaults.scales.linear; + + expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); + }); + + it('Should correctly determine the max & min data values', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, -5, 78, -100] + }, { + yAxisID: 'y2', + data: [-1000, 1000], + }, { + yAxisID: 'y', + data: [150] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear' + }, + y2: { + type: 'linear', + position: 'right', + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-100); + expect(chart.scales.y.max).toBe(150); + }); + + it('Should correctly determine the max & min of string data values', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: ['10', '5', '0', '-5', '78', '-100'] + }, { + yAxisID: 'y2', + data: ['-1000', '1000'], + }, { + yAxisID: 'y', + data: ['150'] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear' + }, + y2: { + type: 'linear', + position: 'right' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-100); + expect(chart.scales.y.max).toBe(150); + }); + + it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + suggestedMin: -10, + suggestedMax: 15 + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-10); + expect(chart.scales.y.max).toBe(15); + }); + + it('Should correctly determine the max & min when no datasets are associated and suggested minimum and maximum are set', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [] + }, + options: { + scales: { + y: { + type: 'linear', + suggestedMin: -10, + suggestedMax: 0 + } + } + } + }); + + expect(chart.scales.y.min).toBe(-10); + expect(chart.scales.y.max).toBe(0); + }); + + it('Should correctly determine the max & min data values ignoring hidden datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: ['10', '5', '0', '-5', '78', '-100'] + }, { + yAxisID: 'y2', + data: ['-1000', '1000'], + }, { + yAxisID: 'y', + data: ['150'], + hidden: true + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear' + }, + y2: { + position: 'right', + type: 'linear' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-100); + expect(chart.scales.y.max).toBe(80); + }); + + it('Should correctly determine the max & min data values ignoring data that is NaN', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [null, 90, NaN, undefined, 45, 30, Infinity, -Infinity] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] + }, + options: { + scales: { + y: { + type: 'linear', + beginAtZero: false + } + } + } + }); + + expect(chart.scales.y.min).toBe(30); + expect(chart.scales.y.max).toBe(90); + + // Scale is now stacked + chart.scales.y.options.stacked = true; + chart.update(); + + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(90); + }); + + it('Should correctly determine the max & min data values for small numbers', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [-1e-8, 3e-8, -4e-8, 6e-8] + }], + labels: ['a', 'b', 'c', 'd'] + }, + options: { + scales: { + y: { + type: 'linear' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min * 1e8).toBeCloseTo(-4); + expect(chart.scales.y.max * 1e8).toBeCloseTo(6); + }); + + it('Should correctly determine the max & min for scatter data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [{ + x: 10, + y: 100 + }, { + x: -10, + y: 0 + }, { + x: 0, + y: 0 + }, { + x: 99, + y: 7 + }] + }], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + chart.update(); + + expect(chart.scales.x.min).toBe(-20); + expect(chart.scales.x.max).toBe(100); + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(100); + }); + + it('Should correctly get the label for the given index', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [{ + x: 10, + y: 100 + }, { + x: -10, + y: 0 + }, { + x: 0, + y: 0 + }, { + x: 99, + y: 7 + }] + }], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + chart.update(); + + expect(chart.scales.y.getLabelForValue(7)).toBe('7'); + }); + + it('Should correctly determine the min and max data values when stacked mode is turned on', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, -5, 78, -100], + type: 'bar' + }, { + yAxisID: 'y2', + data: [-1000, 1000], + }, { + yAxisID: 'y', + data: [150, 0, 0, -100, -10, 9], + type: 'bar' + }, { + yAxisID: 'y', + data: [10, 10, 10, 10, 10, 10], + type: 'line' + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + stacked: true + }, + y2: { + position: 'right', + type: 'linear' + } + } + } + }); + chart.update(); + + expect(chart.scales.y.min).toBe(-150); + expect(chart.scales.y.max).toBe(200); + }); + + it('Should correctly determine the min and max data values when stacked mode is turned on and there are hidden datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, -5, 78, -100], + }, { + yAxisID: 'y2', + data: [-1000, 1000], + }, { + yAxisID: 'y', + data: [150, 0, 0, -100, -10, 9], + }, { + yAxisID: 'y', + data: [10, 20, 30, 40, 50, 60], + hidden: true + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + stacked: true + }, + y2: { + position: 'right', + type: 'linear' + } + } + } + }); + chart.update(); + + expect(chart.scales.y.min).toBe(-150); + expect(chart.scales.y.max).toBe(200); + }); + + it('Should correctly determine the min and max data values when stacked mode is turned on there are multiple types of datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + type: 'bar', + data: [10, 5, 0, -5, 78, -100] + }, { + type: 'line', + data: [10, 10, 10, 10, 10, 10], + }, { + type: 'bar', + data: [150, 0, 0, -100, -10, 9] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + stacked: true + } + } + } + }); + + chart.scales.y.determineDataLimits(); + expect(chart.scales.y.min).toBe(-105); + expect(chart.scales.y.max).toBe(160); + }); + + it('Should ensure that the scale has a max and min that are not equal', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(1); + }); + + it('Should ensure that the scale has a max and min that are not equal when beginAtZero is set', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + beginAtZero: true + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(1); + }); + + it('Should use the suggestedMin and suggestedMax options', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [1, 1, 1, 2, 1, 0] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + suggestedMax: 10, + suggestedMin: -10 + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-10); + expect(chart.scales.y.max).toBe(10); + }); + + it('Should use the min and max options', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [1, 1, 1, 2, 1, 0] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + max: 1010, + min: -1010 + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(-1010); + expect(chart.scales.y.max).toBe(1010); + var labels = getLabels(chart.scales.y); + expect(labels[0]).toBe('-1,010'); + expect(labels[labels.length - 1]).toBe('1,010'); + }); + + it('Should use min, max and stepSize to create fixed spaced ticks', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 3, 6, 8, 3, 1] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + min: 1, + max: 11, + ticks: { + stepSize: 2 + } + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(1); + expect(chart.scales.y.max).toBe(11); + expect(getLabels(chart.scales.y)).toEqual(['1', '3', '5', '7', '9', '11']); + }); + + it('Should create decimal steps if stepSize is a decimal number', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 3, 6, 8, 3, 1] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + ticks: { + stepSize: 2.5 + } + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(10); + expect(getLabels(chart.scales.y)).toEqual(['0', '2.5', '5', '7.5', '10']); + }); + + describe('precision', function() { + it('Should create integer steps if precision is 0', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [0, 1, 2, 1, 0, 1] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + ticks: { + precision: 0 + } + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(2); + expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2']); + }); + + it('Should round the step size to the given number of decimal places', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [0, 0.001, 0.002, 0.003, 0, 0.001] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'linear', + ticks: { + precision: 2 + } + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(0); + expect(chart.scales.y.max).toBe(0.01); + expect(getLabels(chart.scales.y)).toEqual(['0', '0.01']); + }); + }); + + + it('should forcibly include 0 in the range if the beginAtZero option is used', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [20, 30, 40, 50] + }], + labels: ['a', 'b', 'c', 'd'] + }, + options: { + scales: { + y: { + type: 'linear', + beginAtZero: false + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(getLabels(chart.scales.y)).toEqual(['20', '25', '30', '35', '40', '45', '50']); + + chart.scales.y.options.beginAtZero = true; + chart.update(); + expect(getLabels(chart.scales.y)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']); + + chart.data.datasets[0].data = [-20, -30, -40, -50]; + chart.update(); + expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + + chart.scales.y.options.beginAtZero = false; + chart.update(); + expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']); + }); + + it('Should generate tick marks in the correct order in reversed mode', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['a', 'b', 'c', 'd'] + }, + options: { + scales: { + y: { + type: 'linear', + reverse: true + } + } + } + }); + + expect(getLabels(chart.scales.y)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']); + expect(chart.scales.y.start).toBe(80); + expect(chart.scales.y.end).toBe(0); + }); + + it('should use the correct number of decimal places in the default format function', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [0.06, 0.005, 0, 0.025, 0.0078] + }], + labels: ['a', 'b', 'c', 'd'] + }, + options: { + scales: { + y: { + type: 'linear', + } + } + } + }); + expect(getLabels(chart.scales.y)).toEqual(['0', '0.01', '0.02', '0.03', '0.04', '0.05', '0.06']); + }); + + it('Should correctly limit the maximum number of ticks', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [0.5, 2.5] + }] + }, + options: { + scales: { + y: { + beginAtZero: false + } + } + } + }); + + expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.y.ticks.maxTicksLimit = 11; + chart.update(); + + expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.y.ticks.maxTicksLimit = 21; + chart.update(); + + expect(getLabels(chart.scales.y)).toEqual([ + '0.5', + '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5', + '1.6', '1.7', '1.8', '1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5' + ]); + + chart.options.scales.y.ticks.maxTicksLimit = 11; + chart.options.scales.y.ticks.stepSize = 0.01; + chart.update(); + + expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.y.min = 0.3; + chart.options.scales.y.max = 2.8; + chart.update(); + + expect(getLabels(chart.scales.y)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']); + }); + + it('Should bound to data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [1, 99] + }] + }, + options: { + scales: { + y: { + bounds: 'data' + } + } + } + }); + + expect(chart.scales.y.min).toEqual(1); + expect(chart.scales.y.max).toEqual(99); + }); + + it('Should build labels using the user supplied callback', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 0, 25, 78] + }], + labels: ['a', 'b', 'c', 'd'] + }, + options: { + scales: { + y: { + type: 'linear', + ticks: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + // Just the index + expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); + }); + + it('Should get the correct pixel value for a point', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: [-1, 1], + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [-1, 1] + }], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + expect(xScale.getPixelForValue(1)).toBeCloseToPixel(501); // right - paddingRight + expect(xScale.getPixelForValue(-1)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace + expect(xScale.getPixelForValue(0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/ + + expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2); + expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2); + expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2); + + var yScale = chart.scales.y; + expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); // right - paddingRight + expect(yScale.getPixelForValue(-1)).toBeCloseToPixel(484); // left + paddingLeft + expect(yScale.getPixelForValue(0)).toBeCloseToPixel(258); // halfway*/ + + expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2); + expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2); + expect(yScale.getValueForPixel(258)).toBeCloseTo(0, 1e-2); + }); + + it('should fit correctly', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [{ + x: 10, + y: 100 + }, { + x: -10, + y: 0 + }, { + x: 0, + y: 0 + }, { + x: 99, + y: 7 + }] + }], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'linear' + } + } + } + }); + + var xScale = chart.scales.x; + var yScale = chart.scales.y; + expect(xScale.paddingTop).toBeCloseToPixel(0); + expect(xScale.paddingBottom).toBeCloseToPixel(0); + expect(xScale.paddingLeft).toBeCloseToPixel(12); + expect(xScale.paddingRight).toBeCloseToPixel(13.5); + expect(xScale.width).toBeCloseToPixel(468 - 6); // minus lineSpace + expect(xScale.height).toBeCloseToPixel(30); + + expect(yScale.paddingTop).toBeCloseToPixel(7); + expect(yScale.paddingBottom).toBeCloseToPixel(7); + expect(yScale.paddingLeft).toBeCloseToPixel(0); + expect(yScale.paddingRight).toBeCloseToPixel(0); + expect(yScale.width).toBeCloseToPixel(30 + 6); // plus lineSpace + expect(yScale.height).toBeCloseToPixel(450); + + // Extra size when scale label showing + xScale.options.scaleLabel.display = true; + yScale.options.scaleLabel.display = true; + chart.update(); + + expect(xScale.paddingTop).toBeCloseToPixel(0); + expect(xScale.paddingBottom).toBeCloseToPixel(0); + expect(xScale.paddingLeft).toBeCloseToPixel(12); + expect(xScale.paddingRight).toBeCloseToPixel(13.5); + expect(xScale.width).toBeCloseToPixel(440); + expect(xScale.height).toBeCloseToPixel(53); + + expect(yScale.paddingTop).toBeCloseToPixel(7); + expect(yScale.paddingBottom).toBeCloseToPixel(7); + expect(yScale.paddingLeft).toBeCloseToPixel(0); + expect(yScale.paddingRight).toBeCloseToPixel(0); + expect(yScale.width).toBeCloseToPixel(58); + expect(yScale.height).toBeCloseToPixel(427); + }); + + it('should fit correctly when display is turned off', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + yAxisID: 'y', + data: [{ + x: 10, + y: 100 + }, { + x: -10, + y: 0 + }, { + x: 0, + y: 0 + }, { + x: 99, + y: 7 + }] + }], + }, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + }, + y: { + type: 'linear', + gridLines: { + drawTicks: false, + drawBorder: false + }, + scaleLabel: { + display: false, + lineHeight: 1.2 + }, + ticks: { + display: false, + padding: 0 + } + } + } + } + }); + + var yScale = chart.scales.y; + expect(yScale.width).toBeCloseToPixel(0); + }); + + it('max and min value should be valid and finite when charts datasets are hidden', function() { + var barData = { + labels: ['S1', 'S2', 'S3'], + datasets: [{ + label: 'Closed', + backgroundColor: '#382765', + data: [2500, 2000, 1500] + }, { + label: 'In Progress', + backgroundColor: '#7BC225', + data: [1000, 2000, 1500] + }, { + label: 'Assigned', + backgroundColor: '#ffC225', + data: [1000, 2000, 1500] + }] + }; + + var chart = window.acquireChart({ + type: 'bar', + data: barData, + options: { + indexAxis: 'y', + scales: { + x: { + stacked: true + }, + y: { + stacked: true + } + } + } + }); + + barData.datasets.forEach(function(data, index) { + var meta = chart.getDatasetMeta(index); + meta.hidden = true; + chart.update(); + }); + + expect(chart.scales.x.min).toEqual(0); + expect(chart.scales.x.max).toEqual(1); + }); + + it('max and min value should be valid when min is set and all datasets are hidden', function() { + var barData = { + labels: ['S1', 'S2', 'S3'], + datasets: [{ + label: 'dataset 1', + backgroundColor: '#382765', + data: [2500, 2000, 1500], + hidden: true, + }] + }; + + var chart = window.acquireChart({ + type: 'bar', + data: barData, + options: { + indexAxis: 'y', + scales: { + x: { + min: 20 + } + } + } + }); + + expect(chart.scales.x.min).toEqual(20); + expect(chart.scales.x.max).toEqual(21); + }); + + it('min settings should be used if set to zero', function() { + var barData = { + labels: ['S1', 'S2', 'S3'], + datasets: [{ + label: 'dataset 1', + backgroundColor: '#382765', + data: [2500, 2000, 1500] + }] + }; + + var chart = window.acquireChart({ + type: 'bar', + data: barData, + options: { + indexAxis: 'y', + scales: { + x: { + min: 0, + max: 3000 + } + } + } + }); + + expect(chart.scales.x.min).toEqual(0); + }); + + it('max settings should be used if set to zero', function() { + var barData = { + labels: ['S1', 'S2', 'S3'], + datasets: [{ + label: 'dataset 1', + backgroundColor: '#382765', + data: [-2500, -2000, -1500] + }] + }; + + var chart = window.acquireChart({ + type: 'bar', + data: barData, + options: { + indexAxis: 'y', + scales: { + x: { + min: -3000, + max: 0 + } + } + } + }); + + expect(chart.scales.x.max).toEqual(0); + }); + + it('Should get correct pixel values when horizontal', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [0.05, -25, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + indexAxis: 'y', + scales: { + x: { + type: 'linear', + } + } + } + }); + + var start = chart.chartArea.left; + var end = chart.chartArea.right; + var min = -30; + var max = 40; + var scale = chart.scales.x; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); + expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); + + scale.options.reverse = true; + chart.update(); + + start = chart.chartArea.left; + end = chart.chartArea.right; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); + expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); + }); + + it('Should get correct pixel values when vertical', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [0.05, -25, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + scales: { + y: { + type: 'linear', + } + } + } + }); + + var start = chart.chartArea.bottom; + var end = chart.chartArea.top; + var min = -30; + var max = 40; + var scale = chart.scales.y; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); + expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); + + scale.options.reverse = true; + chart.update(); + + start = chart.chartArea.bottom; + end = chart.chartArea.top; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); + expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); + }); }); diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 332860fe5da..0f0dc3303eb 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -1,1168 +1,1168 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } describe('Logarithmic Scale tests', function() { - it('should register', function() { - var Constructor = Chart.registry.getScale('logarithmic'); - expect(Constructor).not.toBe(undefined); - expect(typeof Constructor).toBe('function'); - }); - - it('should have the correct default config', function() { - var defaultConfig = Chart.defaults.scales.logarithmic; - expect(defaultConfig).toEqual({ - ticks: { - callback: Chart.Ticks.formatters.logarithmic, - major: { - enabled: true - } - } - }); - - // Is this actually a function - expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); - }); - - it('should correctly determine the max & min data values', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [42, 1000, 64, 100], - }, { - yAxisID: 'y1', - data: [10, 5, 5000, 78, 450] - }, { - yAxisID: 'y1', - data: [150] - }, { - yAxisID: 'y2', - data: [20, 0, 150, 1800, 3040] - }, { - yAxisID: 'y3', - data: [67, 0.0004, 0, 820, 0.001] - }], - labels: ['a', 'b', 'c', 'd', 'e'] - }, - options: { - scales: { - y: { - id: 'y', - type: 'logarithmic' - }, - y1: { - type: 'logarithmic', - position: 'right' - }, - y2: { - type: 'logarithmic', - position: 'right' - }, - y3: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(10); - expect(chart.scales.y.max).toBe(1000); - - expect(chart.scales.y1).not.toEqual(undefined); // must construct - expect(chart.scales.y1.min).toBe(1); - expect(chart.scales.y1.max).toBe(5000); - - expect(chart.scales.y2).not.toEqual(undefined); // must construct - expect(chart.scales.y2.min).toBe(10); - expect(chart.scales.y2.max).toBe(4000); - - expect(chart.scales.y3).not.toEqual(undefined); // must construct - expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4); - expect(chart.scales.y3.max).toBe(900); - }); - - it('should correctly determine the max & min of string data values', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - yAxisID: 'y', - data: ['42', '1000', '64', '100'], - }, { - yAxisID: 'y1', - data: ['10', '5', '5000', '78', '450'] - }, { - yAxisID: 'y1', - data: ['150'] - }, { - yAxisID: 'y2', - data: ['20', '0', '150', '1800', '3040'] - }, { - yAxisID: 'y3', - data: ['67', '0.0004', '0', '820', '0.001'] - }], - labels: ['a', 'b', 'c', 'd', 'e'] - }, - options: { - scales: { - y: { - type: 'logarithmic' - }, - y1: { - position: 'right', - type: 'logarithmic' - }, - y2: { - position: 'right', - type: 'logarithmic' - }, - y3: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(10); - expect(chart.scales.y.max).toBe(1000); - - expect(chart.scales.y1).not.toEqual(undefined); // must construct - expect(chart.scales.y1.min).toBe(1); - expect(chart.scales.y1.max).toBe(5000); - - expect(chart.scales.y2).not.toEqual(undefined); // must construct - expect(chart.scales.y2.min).toBe(10); - expect(chart.scales.y2.max).toBe(4000); - - expect(chart.scales.y3).not.toEqual(undefined); // must construct - expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4); - expect(chart.scales.y3.max).toBe(900); - }); - - it('should correctly determine the max & min data values when there are hidden datasets', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - yAxisID: 'y1', - data: [10, 5, 5000, 78, 450] - }, { - yAxisID: 'y', - data: [42, 1000, 64, 100], - }, { - yAxisID: 'y1', - data: [50000], - hidden: true - }, { - yAxisID: 'y2', - data: [20, 0, 7400, 14, 291] - }, { - yAxisID: 'y2', - data: [6, 0.0007, 9, 890, 60000], - hidden: true - }], - labels: ['a', 'b', 'c', 'd', 'e'] - }, - options: { - scales: { - y: { - type: 'logarithmic' - }, - y1: { - position: 'right', - type: 'logarithmic' - }, - y2: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y1).not.toEqual(undefined); // must construct - expect(chart.scales.y1.min).toBe(1); - expect(chart.scales.y1.max).toBe(5000); - - expect(chart.scales.y2).not.toEqual(undefined); // must construct - expect(chart.scales.y2.min).toBe(10); - expect(chart.scales.y2.max).toBe(8000); - }); - - it('should correctly determine the max & min data values when there is NaN data', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [undefined, 10, null, 5, 5000, NaN, 78, 450] - }, { - yAxisID: 'y', - data: [undefined, 28, null, 1000, 500, NaN, 50, 42, Infinity, -Infinity] - }, { - yAxisID: 'y1', - data: [undefined, 30, null, 9400, 0, NaN, 54, 836] - }, { - yAxisID: 'y1', - data: [undefined, 0, null, 800, 9, NaN, 894, 21] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] - }, - options: { - scales: { - y: { - type: 'logarithmic' - }, - y1: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(1); - expect(chart.scales.y.max).toBe(5000); - - // Turn on stacked mode since it uses it's own - chart.options.scales.y.stacked = true; - chart.update(); - - expect(chart.scales.y.min).toBe(1); - expect(chart.scales.y.max).toBe(6000); - - expect(chart.scales.y1).not.toEqual(undefined); // must construct - expect(chart.scales.y1.min).toBe(1); - expect(chart.scales.y1.max).toBe(10000); - }); - - it('should correctly determine the max & min for scatter data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [ - {x: 10, y: 100}, - {x: 2, y: 6}, - {x: 65, y: 121}, - {x: 99, y: 7} - ] - }] - }, - options: { - scales: { - x: { - type: 'logarithmic', - position: 'bottom' - }, - y: { - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.x.min).toBe(1); - expect(chart.scales.x.max).toBe(100); - - expect(chart.scales.y.min).toBe(1); - expect(chart.scales.y.max).toBe(200); - }); - - it('should correctly determine the max & min for scatter data when 0 values are present', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [ - {x: 7, y: 950}, - {x: 289, y: 0}, - {x: 0, y: 8}, - {x: 23, y: 0.04} - ] - }] - }, - options: { - scales: { - x: { - type: 'logarithmic', - position: 'bottom' - }, - y: { - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.x.min).toBe(1); - expect(chart.scales.x.max).toBe(30); - - expect(chart.scales.y.min).toBe(0.01); - expect(chart.scales.y.max).toBe(1000); - }); - - it('should correctly determine the min and max data values when stacked mode is turned on', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - type: 'bar', - yAxisID: 'y', - data: [10, 5, 1, 5, 78, 100] - }, { - yAxisID: 'y1', - data: [0, 1000], - }, { - type: 'bar', - yAxisID: 'y', - data: [150, 10, 10, 100, 10, 9] - }, { - type: 'line', - yAxisID: 'y', - data: [100, 100, 100, 100, 100, 100] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'logarithmic', - stacked: true - }, - y1: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y.min).toBe(0.1); - expect(chart.scales.y.max).toBe(200); - }); - - it('should correctly determine the min and max data values when stacked mode is turned on ignoring hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 1, 5, 78, 100], - type: 'bar' - }, { - yAxisID: 'y1', - data: [0, 1000], - type: 'bar' - }, { - yAxisID: 'y', - data: [150, 10, 10, 100, 10, 9], - type: 'bar' - }, { - yAxisID: 'y', - data: [10000, 10000, 10000, 10000, 10000, 10000], - hidden: true, - type: 'bar' - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'logarithmic', - stacked: true - }, - y1: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y.min).toBe(0.1); - expect(chart.scales.y.max).toBe(200); - }); - - it('should ensure that the scale has a max and min that are not equal', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y.min).toBe(1); - expect(chart.scales.y.max).toBe(10); - - chart.data.datasets[0].data = [0.15, 0.15]; - chart.update(); - - expect(chart.scales.y.min).toBe(0.01); - expect(chart.scales.y.max).toBe(1); - }); - - it('should use the min and max options', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [1, 1, 1, 2, 1, 0] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - min: 10, - max: 1010, - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var yScale = chart.scales.y; - var tickCount = yScale.ticks.length; - expect(yScale.min).toBe(10); - expect(yScale.max).toBe(1010); - expect(yScale.ticks[0].value).toBe(10); - expect(yScale.ticks[tickCount - 1].value).toBe(1010); - }); - - 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: { - y: { - type: 'logarithmic', - min: -10, - max: -1010, - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var y = chart.scales.y; - expect(y.min).toBe(0.1); - expect(y.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: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'logarithmic', - min: 'zero', - max: null, - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var y = chart.scales.y; - expect(y.min).toBe(0.1); - expect(y.max).toBe(2); - }); - - it('should generate tick marks', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [10, 5, 2, 25, 78] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var scale = chart.scales.y; - expect(getLabels(scale)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80]); - expect(scale.start).toEqual(1); - expect(scale.end).toEqual(80); - }); - - it('should generate tick marks when 0 values are present', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [11, 0.8, 0, 28, 7] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var scale = chart.scales.y; - // Counts down because the lines are drawn top to bottom - expect(getLabels(scale)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30]); - expect(scale.start).toEqual(0.1); - expect(scale.end).toEqual(30); - }); - - - it('should generate tick marks in the correct order in reversed mode', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 5, 1, 25, 78] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - reverse: true, - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var scale = chart.scales.y; - expect(getLabels(scale)).toEqual([80, 70, 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); - expect(scale.start).toEqual(80); - expect(scale.end).toEqual(1); - }); - - it('should generate tick marks in the correct order in reversed mode when 0 values are present', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [21, 9, 0, 10, 25] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - reverse: true, - ticks: { - callback: function(value) { - return value; - } - } - } - } - } - }); - - var scale = chart.scales.y; - expect(getLabels(scale)).toEqual([30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); - expect(scale.start).toEqual(30); - expect(scale.end).toEqual(1); - }); - - it('should build labels using the default template', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [10, 5, 1.1, 25, 0, 78] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic' - } - } - } - }); - - expect(getLabels(chart.scales.y)).toEqual(['1', '2', '', '', '5', '', '', '', '', '10', '20', '', '', '50', '', '', '']); - }); - - it('should build labels using the user supplied callback', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [10, 5, 2, 25, 78] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic', - ticks: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - // Just the index - expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16']); - }); - - it('should correctly get the correct label for a data item', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [10, 5, 5000, 78, 450] - }, { - yAxisID: 'y1', - data: [1, 1000, 10, 100], - }, { - yAxisID: 'y', - data: [150] - }], - labels: [] - }, - options: { - scales: { - y: { - type: 'logarithmic' - }, - y1: { - position: 'right', - type: 'logarithmic' - } - } - } - }); - - expect(chart.scales.y.getLabelForValue(150)).toBe('150'); - }); - - describe('when', function() { - var data = [ - { - data: [1, 39], - stack: 'stack' - }, - { - data: [1, 39], - stack: 'stack' - }, - ]; - var dataWithEmptyStacks = [ - { - data: [] - }, - { - data: [] - } - ].concat(data); - var config = [ - { - axis: 'y', - firstTick: 1, // start of the axis (minimum) - describe: 'all stacks are defined' - }, - { - axis: 'y', - data: dataWithEmptyStacks, - firstTick: 1, - describe: 'not all stacks are defined' - }, - { - axis: 'y', - scale: { - y: { - min: 0 - } - }, - firstTick: 0.1, - describe: 'all stacks are defined and min: 0' - }, - { - axis: 'y', - data: dataWithEmptyStacks, - scale: { - y: { - min: 0 - } - }, - firstTick: 0.1, - describe: 'not stacks are defined and min: 0' - }, - { - axis: 'x', - firstTick: 1, - describe: 'all stacks are defined' - }, - { - axis: 'x', - data: dataWithEmptyStacks, - firstTick: 1, - describe: 'not all stacks are defined' - }, - { - axis: 'x', - scale: { - x: { - min: 0 - } - }, - firstTick: 0.1, - describe: 'all stacks are defined and min: 0' - }, - { - axis: 'x', - data: dataWithEmptyStacks, - scale: { - x: { - min: 0 - } - }, - firstTick: 0.1, - describe: 'not all stacks are defined and min: 0' - }, - ]; - config.forEach(function(setup) { - var scaleConfig = {}; - var indexAxis, chartStart, chartEnd; - - if (setup.axis === 'x') { - indexAxis = 'y'; - chartStart = 'left'; - chartEnd = 'right'; - } else { - indexAxis = 'x'; - chartStart = 'bottom'; - chartEnd = 'top'; - } - scaleConfig[setup.axis] = { - type: 'logarithmic', - beginAtZero: false - }; - Object.assign(scaleConfig, setup.scale); - scaleConfig[setup.axis].type = 'logarithmic'; - - var description = 'dataset has stack option and ' + setup.describe + it('should register', function() { + var Constructor = Chart.registry.getScale('logarithmic'); + expect(Constructor).not.toBe(undefined); + expect(typeof Constructor).toBe('function'); + }); + + it('should have the correct default config', function() { + var defaultConfig = Chart.defaults.scales.logarithmic; + expect(defaultConfig).toEqual({ + ticks: { + callback: Chart.Ticks.formatters.logarithmic, + major: { + enabled: true + } + } + }); + + // Is this actually a function + expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); + }); + + it('should correctly determine the max & min data values', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [42, 1000, 64, 100], + }, { + yAxisID: 'y1', + data: [10, 5, 5000, 78, 450] + }, { + yAxisID: 'y1', + data: [150] + }, { + yAxisID: 'y2', + data: [20, 0, 150, 1800, 3040] + }, { + yAxisID: 'y3', + data: [67, 0.0004, 0, 820, 0.001] + }], + labels: ['a', 'b', 'c', 'd', 'e'] + }, + options: { + scales: { + y: { + id: 'y', + type: 'logarithmic' + }, + y1: { + type: 'logarithmic', + position: 'right' + }, + y2: { + type: 'logarithmic', + position: 'right' + }, + y3: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(10); + expect(chart.scales.y.max).toBe(1000); + + expect(chart.scales.y1).not.toEqual(undefined); // must construct + expect(chart.scales.y1.min).toBe(1); + expect(chart.scales.y1.max).toBe(5000); + + expect(chart.scales.y2).not.toEqual(undefined); // must construct + expect(chart.scales.y2.min).toBe(10); + expect(chart.scales.y2.max).toBe(4000); + + expect(chart.scales.y3).not.toEqual(undefined); // must construct + expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4); + expect(chart.scales.y3.max).toBe(900); + }); + + it('should correctly determine the max & min of string data values', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + yAxisID: 'y', + data: ['42', '1000', '64', '100'], + }, { + yAxisID: 'y1', + data: ['10', '5', '5000', '78', '450'] + }, { + yAxisID: 'y1', + data: ['150'] + }, { + yAxisID: 'y2', + data: ['20', '0', '150', '1800', '3040'] + }, { + yAxisID: 'y3', + data: ['67', '0.0004', '0', '820', '0.001'] + }], + labels: ['a', 'b', 'c', 'd', 'e'] + }, + options: { + scales: { + y: { + type: 'logarithmic' + }, + y1: { + position: 'right', + type: 'logarithmic' + }, + y2: { + position: 'right', + type: 'logarithmic' + }, + y3: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(10); + expect(chart.scales.y.max).toBe(1000); + + expect(chart.scales.y1).not.toEqual(undefined); // must construct + expect(chart.scales.y1.min).toBe(1); + expect(chart.scales.y1.max).toBe(5000); + + expect(chart.scales.y2).not.toEqual(undefined); // must construct + expect(chart.scales.y2.min).toBe(10); + expect(chart.scales.y2.max).toBe(4000); + + expect(chart.scales.y3).not.toEqual(undefined); // must construct + expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4); + expect(chart.scales.y3.max).toBe(900); + }); + + it('should correctly determine the max & min data values when there are hidden datasets', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + yAxisID: 'y1', + data: [10, 5, 5000, 78, 450] + }, { + yAxisID: 'y', + data: [42, 1000, 64, 100], + }, { + yAxisID: 'y1', + data: [50000], + hidden: true + }, { + yAxisID: 'y2', + data: [20, 0, 7400, 14, 291] + }, { + yAxisID: 'y2', + data: [6, 0.0007, 9, 890, 60000], + hidden: true + }], + labels: ['a', 'b', 'c', 'd', 'e'] + }, + options: { + scales: { + y: { + type: 'logarithmic' + }, + y1: { + position: 'right', + type: 'logarithmic' + }, + y2: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y1).not.toEqual(undefined); // must construct + expect(chart.scales.y1.min).toBe(1); + expect(chart.scales.y1.max).toBe(5000); + + expect(chart.scales.y2).not.toEqual(undefined); // must construct + expect(chart.scales.y2.min).toBe(10); + expect(chart.scales.y2.max).toBe(8000); + }); + + it('should correctly determine the max & min data values when there is NaN data', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [undefined, 10, null, 5, 5000, NaN, 78, 450] + }, { + yAxisID: 'y', + data: [undefined, 28, null, 1000, 500, NaN, 50, 42, Infinity, -Infinity] + }, { + yAxisID: 'y1', + data: [undefined, 30, null, 9400, 0, NaN, 54, 836] + }, { + yAxisID: 'y1', + data: [undefined, 0, null, 800, 9, NaN, 894, 21] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + }, + options: { + scales: { + y: { + type: 'logarithmic' + }, + y1: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(1); + expect(chart.scales.y.max).toBe(5000); + + // Turn on stacked mode since it uses it's own + chart.options.scales.y.stacked = true; + chart.update(); + + expect(chart.scales.y.min).toBe(1); + expect(chart.scales.y.max).toBe(6000); + + expect(chart.scales.y1).not.toEqual(undefined); // must construct + expect(chart.scales.y1.min).toBe(1); + expect(chart.scales.y1.max).toBe(10000); + }); + + it('should correctly determine the max & min for scatter data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [ + {x: 10, y: 100}, + {x: 2, y: 6}, + {x: 65, y: 121}, + {x: 99, y: 7} + ] + }] + }, + options: { + scales: { + x: { + type: 'logarithmic', + position: 'bottom' + }, + y: { + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.x.min).toBe(1); + expect(chart.scales.x.max).toBe(100); + + expect(chart.scales.y.min).toBe(1); + expect(chart.scales.y.max).toBe(200); + }); + + it('should correctly determine the max & min for scatter data when 0 values are present', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [ + {x: 7, y: 950}, + {x: 289, y: 0}, + {x: 0, y: 8}, + {x: 23, y: 0.04} + ] + }] + }, + options: { + scales: { + x: { + type: 'logarithmic', + position: 'bottom' + }, + y: { + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.x.min).toBe(1); + expect(chart.scales.x.max).toBe(30); + + expect(chart.scales.y.min).toBe(0.01); + expect(chart.scales.y.max).toBe(1000); + }); + + it('should correctly determine the min and max data values when stacked mode is turned on', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + type: 'bar', + yAxisID: 'y', + data: [10, 5, 1, 5, 78, 100] + }, { + yAxisID: 'y1', + data: [0, 1000], + }, { + type: 'bar', + yAxisID: 'y', + data: [150, 10, 10, 100, 10, 9] + }, { + type: 'line', + yAxisID: 'y', + data: [100, 100, 100, 100, 100, 100] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'logarithmic', + stacked: true + }, + y1: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y.min).toBe(0.1); + expect(chart.scales.y.max).toBe(200); + }); + + it('should correctly determine the min and max data values when stacked mode is turned on ignoring hidden datasets', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 1, 5, 78, 100], + type: 'bar' + }, { + yAxisID: 'y1', + data: [0, 1000], + type: 'bar' + }, { + yAxisID: 'y', + data: [150, 10, 10, 100, 10, 9], + type: 'bar' + }, { + yAxisID: 'y', + data: [10000, 10000, 10000, 10000, 10000, 10000], + hidden: true, + type: 'bar' + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'logarithmic', + stacked: true + }, + y1: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y.min).toBe(0.1); + expect(chart.scales.y.max).toBe(200); + }); + + it('should ensure that the scale has a max and min that are not equal', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y.min).toBe(1); + expect(chart.scales.y.max).toBe(10); + + chart.data.datasets[0].data = [0.15, 0.15]; + chart.update(); + + expect(chart.scales.y.min).toBe(0.01); + expect(chart.scales.y.max).toBe(1); + }); + + it('should use the min and max options', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 1, 1, 2, 1, 0] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + min: 10, + max: 1010, + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var yScale = chart.scales.y; + var tickCount = yScale.ticks.length; + expect(yScale.min).toBe(10); + expect(yScale.max).toBe(1010); + expect(yScale.ticks[0].value).toBe(10); + expect(yScale.ticks[tickCount - 1].value).toBe(1010); + }); + + 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: { + y: { + type: 'logarithmic', + min: -10, + max: -1010, + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var y = chart.scales.y; + expect(y.min).toBe(0.1); + expect(y.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: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'logarithmic', + min: 'zero', + max: null, + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var y = chart.scales.y; + expect(y.min).toBe(0.1); + expect(y.max).toBe(2); + }); + + it('should generate tick marks', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [10, 5, 2, 25, 78] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var scale = chart.scales.y; + expect(getLabels(scale)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80]); + expect(scale.start).toEqual(1); + expect(scale.end).toEqual(80); + }); + + it('should generate tick marks when 0 values are present', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [11, 0.8, 0, 28, 7] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var scale = chart.scales.y; + // Counts down because the lines are drawn top to bottom + expect(getLabels(scale)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30]); + expect(scale.start).toEqual(0.1); + expect(scale.end).toEqual(30); + }); + + + it('should generate tick marks in the correct order in reversed mode', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 5, 1, 25, 78] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + reverse: true, + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var scale = chart.scales.y; + expect(getLabels(scale)).toEqual([80, 70, 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); + expect(scale.start).toEqual(80); + expect(scale.end).toEqual(1); + }); + + it('should generate tick marks in the correct order in reversed mode when 0 values are present', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [21, 9, 0, 10, 25] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + reverse: true, + ticks: { + callback: function(value) { + return value; + } + } + } + } + } + }); + + var scale = chart.scales.y; + expect(getLabels(scale)).toEqual([30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); + expect(scale.start).toEqual(30); + expect(scale.end).toEqual(1); + }); + + it('should build labels using the default template', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 5, 1.1, 25, 0, 78] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic' + } + } + } + }); + + expect(getLabels(chart.scales.y)).toEqual(['1', '2', '', '', '5', '', '', '', '', '10', '20', '', '', '50', '', '', '']); + }); + + it('should build labels using the user supplied callback', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [10, 5, 2, 25, 78] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic', + ticks: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + // Just the index + expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16']); + }); + + it('should correctly get the correct label for a data item', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [10, 5, 5000, 78, 450] + }, { + yAxisID: 'y1', + data: [1, 1000, 10, 100], + }, { + yAxisID: 'y', + data: [150] + }], + labels: [] + }, + options: { + scales: { + y: { + type: 'logarithmic' + }, + y1: { + position: 'right', + type: 'logarithmic' + } + } + } + }); + + expect(chart.scales.y.getLabelForValue(150)).toBe('150'); + }); + + describe('when', function() { + var data = [ + { + data: [1, 39], + stack: 'stack' + }, + { + data: [1, 39], + stack: 'stack' + }, + ]; + var dataWithEmptyStacks = [ + { + data: [] + }, + { + data: [] + } + ].concat(data); + var config = [ + { + axis: 'y', + firstTick: 1, // start of the axis (minimum) + describe: 'all stacks are defined' + }, + { + axis: 'y', + data: dataWithEmptyStacks, + firstTick: 1, + describe: 'not all stacks are defined' + }, + { + axis: 'y', + scale: { + y: { + min: 0 + } + }, + firstTick: 0.1, + describe: 'all stacks are defined and min: 0' + }, + { + axis: 'y', + data: dataWithEmptyStacks, + scale: { + y: { + min: 0 + } + }, + firstTick: 0.1, + describe: 'not stacks are defined and min: 0' + }, + { + axis: 'x', + firstTick: 1, + describe: 'all stacks are defined' + }, + { + axis: 'x', + data: dataWithEmptyStacks, + firstTick: 1, + describe: 'not all stacks are defined' + }, + { + axis: 'x', + scale: { + x: { + min: 0 + } + }, + firstTick: 0.1, + describe: 'all stacks are defined and min: 0' + }, + { + axis: 'x', + data: dataWithEmptyStacks, + scale: { + x: { + min: 0 + } + }, + firstTick: 0.1, + describe: 'not all stacks are defined and min: 0' + }, + ]; + config.forEach(function(setup) { + var scaleConfig = {}; + var indexAxis, chartStart, chartEnd; + + if (setup.axis === 'x') { + indexAxis = 'y'; + chartStart = 'left'; + chartEnd = 'right'; + } else { + indexAxis = 'x'; + chartStart = 'bottom'; + chartEnd = 'top'; + } + scaleConfig[setup.axis] = { + type: 'logarithmic', + beginAtZero: false + }; + Object.assign(scaleConfig, setup.scale); + scaleConfig[setup.axis].type = 'logarithmic'; + + var description = 'dataset has stack option and ' + setup.describe + ' and axis is "' + setup.axis + '";'; - describe(description, function() { - it('should define the correct axis limits', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - labels: ['category 1', 'category 2'], - datasets: setup.data || data, - }, - options: { - indexAxis, - scales: scaleConfig - } - }); - - var axisID = setup.axis; - var scale = chart.scales[axisID]; - var firstTick = setup.firstTick; - var lastTick = 80; // last tick (should be first available tick after: 2 * 39) - var start = chart.chartArea[chartStart]; - var end = chart.chartArea[chartEnd]; - - expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - - expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); - expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); - - chart.scales[axisID].options.reverse = true; // Reverse mode - chart.update(); - - // chartArea might have been resized in update - start = chart.chartArea[chartEnd]; - end = chart.chartArea[chartStart]; - - expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - - expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); - expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); - }); - }); - }); - }); - - describe('when', function() { - var config = [ - { - dataset: [], - firstTick: 1, // value of the first tick - lastTick: 10, // value of the last tick - describe: 'empty dataset, without min/max' - }, - { - dataset: [], - scale: {stacked: true}, - firstTick: 1, - lastTick: 10, - describe: 'empty dataset, without min/max, with stacked: true' - }, - { - data: { - datasets: [ - {data: [], stack: 'stack'}, - {data: [], stack: 'stack'}, - ], - }, - type: 'bar', - firstTick: 1, - lastTick: 10, - describe: 'empty dataset with stack option, without min/max' - }, - { - dataset: [], - scale: {min: 1}, - firstTick: 1, - lastTick: 10, - describe: 'empty dataset, min: 1, without max' - }, - { - dataset: [], - scale: {max: 80}, - firstTick: 1, - lastTick: 80, - describe: 'empty dataset, max: 80, without min' - }, - { - dataset: [], - scale: {max: 0.8}, - firstTick: 0.01, - lastTick: 0.8, - describe: 'empty dataset, max: 0.8, without min' - }, - { - dataset: [{x: 10, y: 10}, {x: 5, y: 5}, {x: 1, y: 1}, {x: 25, y: 25}, {x: 78, y: 78}], - firstTick: 1, - lastTick: 80, - describe: 'dataset min point {x: 1, y: 1}, max point {x:78, y:78}' - }, - ]; - config.forEach(function(setup) { - var axes = [ - { - id: 'x', // horizontal scale - start: 'left', - end: 'right' - }, - { - id: 'y', // vertical scale - start: 'bottom', - end: 'top' - } - ]; - axes.forEach(function(axis) { - var expectation = 'min = ' + setup.firstTick + ', max = ' + setup.lastTick; - describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() { - beforeEach(function() { - var xConfig = { - type: 'logarithmic', - position: 'bottom' - }; - var yConfig = { - type: 'logarithmic', - position: 'left' - }; - var data = setup.data || { - datasets: [{ - data: setup.dataset - }], - }; - Object.assign(xConfig, setup.scale); - Object.assign(yConfig, setup.scale); - Object.assign(data, setup.data || {}); - this.chart = window.acquireChart({ - type: 'line', - data: data, - options: { - scales: { - x: xConfig, - y: yConfig - } - } - }); - }); - - it('should get the correct pixel value for a point', function() { - var chart = this.chart; - var axisID = axis.id; - var scale = chart.scales[axisID]; - var firstTick = setup.firstTick; - var lastTick = setup.lastTick; - var start = chart.chartArea[axis.start]; - var end = chart.chartArea[axis.end]; - - expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - expect(scale.getPixelForValue(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); - - chart.scales[axisID].options.reverse = true; // Reverse mode - chart.update(); - - // chartArea might have been resized in update - start = chart.chartArea[axis.end]; - end = chart.chartArea[axis.start]; - - expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - - expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); - expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); - }); - }); - }); - }); - }); - - describe('when', function() { - var config = [ - { - dataset: [], - scale: {min: 0}, - lastTick: 10, // value of the last tick - describe: 'empty dataset, min: 0, without max' - }, - { - dataset: [], - scale: {min: 0, max: 80}, - lastTick: 80, - describe: 'empty dataset, min: 0, max: 80' - }, - { - dataset: [], - scale: {min: 0, max: 0.8}, - lastTick: 0.8, - describe: 'empty dataset, min: 0, max: 0.8' - }, - { - dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}], - lastTick: 80, - describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}' - }, - { - dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}], - lastTick: 80, - describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}' - }, - { - dataset: [{x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}], - scale: {min: 0}, - lastTick: 80, - describe: 'dataset min point {x: 1.2, y: 1.2}, max point {x:78, y:78}, min: 0' - }, - { - dataset: [{x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}], - scale: {min: 0}, - lastTick: 80, - describe: 'dataset min point {x: 6.3, y: 6.3}, max point {x:78, y:78}, min: 0' - }, - ]; - config.forEach(function(setup) { - var axes = [ - { - id: 'x', // horizontal scale - start: 'left', - end: 'right' - }, - { - id: 'y', // vertical scale - start: 'bottom', - end: 'top' - } - ]; - axes.forEach(function(axis) { - var expectation = 'min = 0, max = ' + setup.lastTick; - describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() { - beforeEach(function() { - var xConfig = { - type: 'logarithmic', - position: 'bottom' - }; - var yConfig = { - type: 'logarithmic', - position: 'left' - }; - var data = setup.data || { - datasets: [{ - data: setup.dataset - }], - }; - Object.assign(xConfig, setup.scale); - Object.assign(yConfig, setup.scale); - Object.assign(data, setup.data || {}); - this.chart = window.acquireChart({ - type: 'line', - data: data, - options: { - scales: { - x: xConfig, - y: yConfig - } - } - }); - }); - - it('should get the correct pixel value for a point', function() { - var chart = this.chart; - var axisID = axis.id; - var scale = chart.scales[axisID]; - var lastTick = setup.lastTick; - var start = chart.chartArea[axis.start]; - var end = chart.chartArea[axis.end]; - - expect(scale.getPixelForValue(0)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - - expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4); - expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); - - chart.scales[axisID].options.reverse = true; // Reverse mode - chart.update(); - - // chartArea might have been resized in update - start = chart.chartArea[axis.end]; - end = chart.chartArea[axis.start]; - - expect(scale.getPixelForValue(0)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); - - expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4); - expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); - }); - }); - }); - }); - }); - - it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - yAxisID: 'y', - data: [] - }], - labels: ['a', 'b', 'c', 'd', 'e', 'f'] - }, - options: { - scales: { - y: { - type: 'logarithmic', - suggestedMin: 10, - suggestedMax: 100 - } - } - } - }); - - expect(chart.scales.y).not.toEqual(undefined); // must construct - expect(chart.scales.y.min).toBe(10); - expect(chart.scales.y.max).toBe(100); - }); - - it('Should bound to data', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['a', 'b'], - datasets: [{ - data: [1.1, 99] - }] - }, - options: { - scales: { - y: { - type: 'logarithmic', - bounds: 'data' - } - } - } - }); - - expect(chart.scales.y.min).toEqual(1.1); - expect(chart.scales.y.max).toEqual(99); - }); + describe(description, function() { + it('should define the correct axis limits', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + labels: ['category 1', 'category 2'], + datasets: setup.data || data, + }, + options: { + indexAxis, + scales: scaleConfig + } + }); + + var axisID = setup.axis; + var scale = chart.scales[axisID]; + var firstTick = setup.firstTick; + var lastTick = 80; // last tick (should be first available tick after: 2 * 39) + var start = chart.chartArea[chartStart]; + var end = chart.chartArea[chartEnd]; + + expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + + expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); + expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); + + chart.scales[axisID].options.reverse = true; // Reverse mode + chart.update(); + + // chartArea might have been resized in update + start = chart.chartArea[chartEnd]; + end = chart.chartArea[chartStart]; + + expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + + expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); + expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); + }); + }); + }); + }); + + describe('when', function() { + var config = [ + { + dataset: [], + firstTick: 1, // value of the first tick + lastTick: 10, // value of the last tick + describe: 'empty dataset, without min/max' + }, + { + dataset: [], + scale: {stacked: true}, + firstTick: 1, + lastTick: 10, + describe: 'empty dataset, without min/max, with stacked: true' + }, + { + data: { + datasets: [ + {data: [], stack: 'stack'}, + {data: [], stack: 'stack'}, + ], + }, + type: 'bar', + firstTick: 1, + lastTick: 10, + describe: 'empty dataset with stack option, without min/max' + }, + { + dataset: [], + scale: {min: 1}, + firstTick: 1, + lastTick: 10, + describe: 'empty dataset, min: 1, without max' + }, + { + dataset: [], + scale: {max: 80}, + firstTick: 1, + lastTick: 80, + describe: 'empty dataset, max: 80, without min' + }, + { + dataset: [], + scale: {max: 0.8}, + firstTick: 0.01, + lastTick: 0.8, + describe: 'empty dataset, max: 0.8, without min' + }, + { + dataset: [{x: 10, y: 10}, {x: 5, y: 5}, {x: 1, y: 1}, {x: 25, y: 25}, {x: 78, y: 78}], + firstTick: 1, + lastTick: 80, + describe: 'dataset min point {x: 1, y: 1}, max point {x:78, y:78}' + }, + ]; + config.forEach(function(setup) { + var axes = [ + { + id: 'x', // horizontal scale + start: 'left', + end: 'right' + }, + { + id: 'y', // vertical scale + start: 'bottom', + end: 'top' + } + ]; + axes.forEach(function(axis) { + var expectation = 'min = ' + setup.firstTick + ', max = ' + setup.lastTick; + describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() { + beforeEach(function() { + var xConfig = { + type: 'logarithmic', + position: 'bottom' + }; + var yConfig = { + type: 'logarithmic', + position: 'left' + }; + var data = setup.data || { + datasets: [{ + data: setup.dataset + }], + }; + Object.assign(xConfig, setup.scale); + Object.assign(yConfig, setup.scale); + Object.assign(data, setup.data || {}); + this.chart = window.acquireChart({ + type: 'line', + data: data, + options: { + scales: { + x: xConfig, + y: yConfig + } + } + }); + }); + + it('should get the correct pixel value for a point', function() { + var chart = this.chart; + var axisID = axis.id; + var scale = chart.scales[axisID]; + var firstTick = setup.firstTick; + var lastTick = setup.lastTick; + var start = chart.chartArea[axis.start]; + var end = chart.chartArea[axis.end]; + + expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(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); + + chart.scales[axisID].options.reverse = true; // Reverse mode + chart.update(); + + // chartArea might have been resized in update + start = chart.chartArea[axis.end]; + end = chart.chartArea[axis.start]; + + expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + + expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); + expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); + }); + }); + }); + }); + }); + + describe('when', function() { + var config = [ + { + dataset: [], + scale: {min: 0}, + lastTick: 10, // value of the last tick + describe: 'empty dataset, min: 0, without max' + }, + { + dataset: [], + scale: {min: 0, max: 80}, + lastTick: 80, + describe: 'empty dataset, min: 0, max: 80' + }, + { + dataset: [], + scale: {min: 0, max: 0.8}, + lastTick: 0.8, + describe: 'empty dataset, min: 0, max: 0.8' + }, + { + dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}], + lastTick: 80, + describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}' + }, + { + dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}], + lastTick: 80, + describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}' + }, + { + dataset: [{x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}], + scale: {min: 0}, + lastTick: 80, + describe: 'dataset min point {x: 1.2, y: 1.2}, max point {x:78, y:78}, min: 0' + }, + { + dataset: [{x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}], + scale: {min: 0}, + lastTick: 80, + describe: 'dataset min point {x: 6.3, y: 6.3}, max point {x:78, y:78}, min: 0' + }, + ]; + config.forEach(function(setup) { + var axes = [ + { + id: 'x', // horizontal scale + start: 'left', + end: 'right' + }, + { + id: 'y', // vertical scale + start: 'bottom', + end: 'top' + } + ]; + axes.forEach(function(axis) { + var expectation = 'min = 0, max = ' + setup.lastTick; + describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() { + beforeEach(function() { + var xConfig = { + type: 'logarithmic', + position: 'bottom' + }; + var yConfig = { + type: 'logarithmic', + position: 'left' + }; + var data = setup.data || { + datasets: [{ + data: setup.dataset + }], + }; + Object.assign(xConfig, setup.scale); + Object.assign(yConfig, setup.scale); + Object.assign(data, setup.data || {}); + this.chart = window.acquireChart({ + type: 'line', + data: data, + options: { + scales: { + x: xConfig, + y: yConfig + } + } + }); + }); + + it('should get the correct pixel value for a point', function() { + var chart = this.chart; + var axisID = axis.id; + var scale = chart.scales[axisID]; + var lastTick = setup.lastTick; + var start = chart.chartArea[axis.start]; + var end = chart.chartArea[axis.end]; + + expect(scale.getPixelForValue(0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + + expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4); + expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); + + chart.scales[axisID].options.reverse = true; // Reverse mode + chart.update(); + + // chartArea might have been resized in update + start = chart.chartArea[axis.end]; + end = chart.chartArea[axis.start]; + + expect(scale.getPixelForValue(0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end); + + expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4); + expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); + }); + }); + }); + }); + }); + + it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'y', + data: [] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + y: { + type: 'logarithmic', + suggestedMin: 10, + suggestedMax: 100 + } + } + } + }); + + expect(chart.scales.y).not.toEqual(undefined); // must construct + expect(chart.scales.y.min).toBe(10); + expect(chart.scales.y.max).toBe(100); + }); + + it('Should bound to data', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [1.1, 99] + }] + }, + options: { + scales: { + y: { + type: 'logarithmic', + bounds: 'data' + } + } + } + }); + + expect(chart.scales.y.min).toEqual(1.1); + expect(chart.scales.y.max).toEqual(99); + }); }); diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index be0c701ae6b..745e95d1a87 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -1,591 +1,591 @@ function getLabels(scale) { - return scale.ticks.map(t => t.label); + return scale.ticks.map(t => t.label); } // 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 registry', function() { - var Constructor = Chart.registry.getScale('radialLinear'); - expect(Constructor).not.toBe(undefined); - expect(typeof Constructor).toBe('function'); - }); - - it('Should have the correct default config', function() { - var defaultConfig = Chart.defaults.scales.radialLinear; - expect(defaultConfig).toEqual({ - display: true, - animate: true, - position: 'chartArea', - - angleLines: { - display: true, - color: 'rgba(0,0,0,0.1)', - lineWidth: 1, - borderDash: [], - borderDashOffset: 0.0 - }, - - gridLines: { - circular: false - }, - - ticks: { - color: Chart.defaults.color, - showLabelBackdrop: true, - backdropColor: 'rgba(255,255,255,0.75)', - backdropPaddingY: 2, - backdropPaddingX: 2, - callback: defaultConfig.ticks.callback - }, - - pointLabels: { - color: Chart.defaults.color, - display: true, - font: { - size: 10 - }, - callback: defaultConfig.pointLabels.callback - } - }); - - // Is this actually a function - expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); - expect(defaultConfig.pointLabels.callback).toEqual(jasmine.any(Function)); - }); - - it('Should correctly determine the max & min data values', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, -5, 78, -100] - }, { - data: [150] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] - }, - options: { - scales: {} - } - }); - - expect(chart.scales.r.min).toBe(-100); - expect(chart.scales.r.max).toBe(150); - }); - - it('Should correctly determine the max & min of string data values', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: ['10', '5', '0', '-5', '78', '-100'] - }, { - data: ['150'] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] - }, - options: { - scales: {} - } - }); - - expect(chart.scales.r.min).toBe(-100); - expect(chart.scales.r.max).toBe(150); - }); - - it('Should correctly determine the max & min data values when there are hidden datasets', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: ['10', '5', '0', '-5', '78', '-100'] - }, { - data: ['150'] - }, { - data: [1000], - hidden: true - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] - }, - options: { - scales: {} - } - }); - - expect(chart.scales.r.min).toBe(-100); - expect(chart.scales.r.max).toBe(150); - }); - - it('Should correctly determine the max & min data values when there is NaN data', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [50, 60, NaN, 70, null, undefined, Infinity, -Infinity] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6', 'label7', 'label8'] - }, - options: { - scales: {} - } - }); - - expect(chart.scales.r.min).toBe(50); - expect(chart.scales.r.max).toBe(70); - }); - - it('Should ensure that the scale has a max and min that are not equal', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [], - labels: [] - }, - options: { - scales: { - rScale: {} - } - } - }); - - var scale = chart.scales.rScale; - - expect(scale.min).toBe(-1); - expect(scale.max).toBe(1); - }); - - it('Should use the suggestedMin and suggestedMax options', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [1, 1, 1, 2, 1, 0] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] - }, - options: { - scales: { - r: { - suggestedMin: -10, - suggestedMax: 10 - } - } - } - }); - - expect(chart.scales.r.min).toBe(-10); - expect(chart.scales.r.max).toBe(10); - }); - - it('Should use the min and max options', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [1, 1, 1, 2, 1, 0] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] - }, - options: { - scales: { - r: { - min: -1010, - max: 1010 - } - } - } - }); - - expect(chart.scales.r.min).toBe(-1010); - expect(chart.scales.r.max).toBe(1010); - expect(getLabels(chart.scales.r)).toEqual(['-1,010', '-1,000', '-500', '0', '500', '1,000', '1,010']); - }); - - it('should forcibly include 0 in the range if the beginAtZero option is used', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [20, 30, 40, 50] - }], - labels: ['label1', 'label2', 'label3', 'label4'] - }, - options: { - scales: { - r: { - beginAtZero: false - } - } - } - }); - - expect(getLabels(chart.scales.r)).toEqual(['20', '25', '30', '35', '40', '45', '50']); - - chart.scales.r.options.beginAtZero = true; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']); - - chart.data.datasets[0].data = [-20, -30, -40, -50]; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); - - chart.scales.r.options.beginAtZero = false; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']); - }); - - it('Should generate tick marks in the correct order in reversed mode', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - reverse: true - } - } - } - }); - - expect(getLabels(chart.scales.r)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']); - expect(chart.scales.r.start).toBe(80); - expect(chart.scales.r.end).toBe(0); - }); - - it('Should correctly limit the maximum number of ticks', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - labels: ['label1', 'label2', 'label3'], - datasets: [{ - data: [0.5, 1.5, 2.5] - }] - }, - options: { - scales: { - r: { - pointLabels: { - display: false - } - } - } - } - }); - - expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.r.ticks.maxTicksLimit = 11; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.r.ticks.stepSize = 0.01; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); - - chart.options.scales.r.min = 0.3; - chart.options.scales.r.max = 2.8; - chart.update(); - - expect(getLabels(chart.scales.r)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']); - }); - - it('Should build labels using the user supplied callback', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - ticks: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - expect(getLabels(chart.scales.r)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); - expect(chart.scales.r.pointLabels).toEqual(['label1', 'label2', 'label3', 'label4', 'label5']); - }); - - it('Should build point labels using the user supplied callback', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - expect(chart.scales.r.pointLabels).toEqual(['0', '1', '2', '3', '4']); - }); - - it('Should build point labels from falsy values', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78, 20] - }], - labels: [0, '', undefined, null, NaN, false] - } - }); - - expect(chart.scales.r.pointLabels).toEqual([0, '', '', '', '', '']); - }); - - it('should correctly set the center point', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - expect(chart.scales.r.drawingArea).toBe(227); - expect(chart.scales.r.xCenter).toBe(256); - expect(chart.scales.r.yCenter).toBe(284); - }); - - it('should correctly get the label for a given data index', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - expect(chart.scales.r.getLabelForValue(5)).toBe('5'); - }); - - it('should get the correct distance from the center point', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(0); - expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(227); - - var position = chart.scales.r.getPointPositionForValue(1, 5); - expect(position.x).toBeCloseToPixel(270); - expect(position.y).toBeCloseToPixel(278); - - chart.scales.r.options.reverse = true; - chart.update(); - - expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(227); - expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(0); - }); - - it('should get the correct value for a distance from the center point', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - } - } - }); - - expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.min); - expect(chart.scales.r.getValueForDistanceFromCenter(227)).toBe(chart.scales.r.max); - - var dist = chart.scales.r.getDistanceFromCenterForValue(5); - expect(chart.scales.r.getValueForDistanceFromCenter(dist)).toBe(5); - - chart.scales.r.options.reverse = true; - chart.update(); - - expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.max); - expect(chart.scales.r.getValueForDistanceFromCenter(227)).toBe(chart.scales.r.min); - }); - - it('should correctly get angles for all points', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - } - } - }, - startAngle: 15 - } - }); - - var radToNearestDegree = function(rad) { - return Math.round((360 * rad) / (2 * Math.PI)); - }; - - var slice = 72; // (360 / 5) - - for (var i = 0; i < 5; i++) { - expect(radToNearestDegree(chart.scales.r.getIndexAngle(i))).toBe(15 + (slice * i)); - } - - chart.options.startAngle = 0; - chart.update(); - - for (var x = 0; x < 5; x++) { - expect(radToNearestDegree(chart.scales.r.getIndexAngle(x))).toBe((slice * x)); - } - }); - - it('should correctly get the correct label alignment for all points', function() { - var chart = window.acquireChart({ - type: 'radar', - data: { - datasets: [{ - data: [10, 5, 0, 25, 78] - }], - labels: ['label1', 'label2', 'label3', 'label4', 'label5'] - }, - options: { - scales: { - r: { - pointLabels: { - callback: function(value, index) { - return index.toString(); - } - }, - ticks: { - display: false - } - } - } - } - }); - - var scale = chart.scales.r; - - [{ - startAngle: 30, - textAlign: ['right', 'right', 'left', 'left', 'left'], - y: [82, 366, 506, 319, 53] - }, { - startAngle: -30, - textAlign: ['right', 'right', 'left', 'left', 'right'], - y: [319, 506, 366, 82, 53] - }, { - startAngle: 750, - textAlign: ['right', 'right', 'left', 'left', 'left'], - y: [82, 366, 506, 319, 53] - }].forEach(function(expected) { - chart.options.startAngle = expected.startAngle; - chart.update(); - - scale.ctx = window.createMockContext(); - chart.draw(); - - scale.ctx.getCalls().filter(function(x) { - return x.name === 'setTextAlign'; - }).forEach(function(x, i) { - expect(x.args[0]).withContext('startAngle: ' + expected.startAngle + ', tick: ' + i).toBe(expected.textAlign[i]); - }); - - scale.ctx.getCalls().filter(function(x) { - return x.name === 'fillText'; - }).map(function(x, i) { - expect(x.args[2]).toBeCloseToPixel(expected.y[i]); - }); - }); - }); + describe('auto', jasmine.fixture.specs('scale.radialLinear')); + + it('Should register the constructor with the registry', function() { + var Constructor = Chart.registry.getScale('radialLinear'); + expect(Constructor).not.toBe(undefined); + expect(typeof Constructor).toBe('function'); + }); + + it('Should have the correct default config', function() { + var defaultConfig = Chart.defaults.scales.radialLinear; + expect(defaultConfig).toEqual({ + display: true, + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0,0,0,0.1)', + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, + + gridLines: { + circular: false + }, + + ticks: { + color: Chart.defaults.color, + showLabelBackdrop: true, + backdropColor: 'rgba(255,255,255,0.75)', + backdropPaddingY: 2, + backdropPaddingX: 2, + callback: defaultConfig.ticks.callback + }, + + pointLabels: { + color: Chart.defaults.color, + display: true, + font: { + size: 10 + }, + callback: defaultConfig.pointLabels.callback + } + }); + + // Is this actually a function + expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function)); + expect(defaultConfig.pointLabels.callback).toEqual(jasmine.any(Function)); + }); + + it('Should correctly determine the max & min data values', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, -5, 78, -100] + }, { + data: [150] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] + }, + options: { + scales: {} + } + }); + + expect(chart.scales.r.min).toBe(-100); + expect(chart.scales.r.max).toBe(150); + }); + + it('Should correctly determine the max & min of string data values', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: ['10', '5', '0', '-5', '78', '-100'] + }, { + data: ['150'] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] + }, + options: { + scales: {} + } + }); + + expect(chart.scales.r.min).toBe(-100); + expect(chart.scales.r.max).toBe(150); + }); + + it('Should correctly determine the max & min data values when there are hidden datasets', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: ['10', '5', '0', '-5', '78', '-100'] + }, { + data: ['150'] + }, { + data: [1000], + hidden: true + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] + }, + options: { + scales: {} + } + }); + + expect(chart.scales.r.min).toBe(-100); + expect(chart.scales.r.max).toBe(150); + }); + + it('Should correctly determine the max & min data values when there is NaN data', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [50, 60, NaN, 70, null, undefined, Infinity, -Infinity] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6', 'label7', 'label8'] + }, + options: { + scales: {} + } + }); + + expect(chart.scales.r.min).toBe(50); + expect(chart.scales.r.max).toBe(70); + }); + + it('Should ensure that the scale has a max and min that are not equal', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [], + labels: [] + }, + options: { + scales: { + rScale: {} + } + } + }); + + var scale = chart.scales.rScale; + + expect(scale.min).toBe(-1); + expect(scale.max).toBe(1); + }); + + it('Should use the suggestedMin and suggestedMax options', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [1, 1, 1, 2, 1, 0] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] + }, + options: { + scales: { + r: { + suggestedMin: -10, + suggestedMax: 10 + } + } + } + }); + + expect(chart.scales.r.min).toBe(-10); + expect(chart.scales.r.max).toBe(10); + }); + + it('Should use the min and max options', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [1, 1, 1, 2, 1, 0] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6'] + }, + options: { + scales: { + r: { + min: -1010, + max: 1010 + } + } + } + }); + + expect(chart.scales.r.min).toBe(-1010); + expect(chart.scales.r.max).toBe(1010); + expect(getLabels(chart.scales.r)).toEqual(['-1,010', '-1,000', '-500', '0', '500', '1,000', '1,010']); + }); + + it('should forcibly include 0 in the range if the beginAtZero option is used', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [20, 30, 40, 50] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + scales: { + r: { + beginAtZero: false + } + } + } + }); + + expect(getLabels(chart.scales.r)).toEqual(['20', '25', '30', '35', '40', '45', '50']); + + chart.scales.r.options.beginAtZero = true; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']); + + chart.data.datasets[0].data = [-20, -30, -40, -50]; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + + chart.scales.r.options.beginAtZero = false; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']); + }); + + it('Should generate tick marks in the correct order in reversed mode', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + reverse: true + } + } + } + }); + + expect(getLabels(chart.scales.r)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']); + expect(chart.scales.r.start).toBe(80); + expect(chart.scales.r.end).toBe(0); + }); + + it('Should correctly limit the maximum number of ticks', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [0.5, 1.5, 2.5] + }] + }, + options: { + scales: { + r: { + pointLabels: { + display: false + } + } + } + } + }); + + expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.r.ticks.maxTicksLimit = 11; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.r.ticks.stepSize = 0.01; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scales.r.min = 0.3; + chart.options.scales.r.max = 2.8; + chart.update(); + + expect(getLabels(chart.scales.r)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']); + }); + + it('Should build labels using the user supplied callback', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + ticks: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + expect(getLabels(chart.scales.r)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); + expect(chart.scales.r.pointLabels).toEqual(['label1', 'label2', 'label3', 'label4', 'label5']); + }); + + it('Should build point labels using the user supplied callback', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + expect(chart.scales.r.pointLabels).toEqual(['0', '1', '2', '3', '4']); + }); + + it('Should build point labels from falsy values', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78, 20] + }], + labels: [0, '', undefined, null, NaN, false] + } + }); + + expect(chart.scales.r.pointLabels).toEqual([0, '', '', '', '', '']); + }); + + it('should correctly set the center point', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + expect(chart.scales.r.drawingArea).toBe(227); + expect(chart.scales.r.xCenter).toBe(256); + expect(chart.scales.r.yCenter).toBe(284); + }); + + it('should correctly get the label for a given data index', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + expect(chart.scales.r.getLabelForValue(5)).toBe('5'); + }); + + it('should get the correct distance from the center point', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(0); + expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(227); + + var position = chart.scales.r.getPointPositionForValue(1, 5); + expect(position.x).toBeCloseToPixel(270); + expect(position.y).toBeCloseToPixel(278); + + chart.scales.r.options.reverse = true; + chart.update(); + + expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(227); + expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(0); + }); + + it('should get the correct value for a distance from the center point', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + } + } + }); + + expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.min); + expect(chart.scales.r.getValueForDistanceFromCenter(227)).toBe(chart.scales.r.max); + + var dist = chart.scales.r.getDistanceFromCenterForValue(5); + expect(chart.scales.r.getValueForDistanceFromCenter(dist)).toBe(5); + + chart.scales.r.options.reverse = true; + chart.update(); + + expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.max); + expect(chart.scales.r.getValueForDistanceFromCenter(227)).toBe(chart.scales.r.min); + }); + + it('should correctly get angles for all points', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + } + } + }, + startAngle: 15 + } + }); + + var radToNearestDegree = function(rad) { + return Math.round((360 * rad) / (2 * Math.PI)); + }; + + var slice = 72; // (360 / 5) + + for (var i = 0; i < 5; i++) { + expect(radToNearestDegree(chart.scales.r.getIndexAngle(i))).toBe(15 + (slice * i)); + } + + chart.options.startAngle = 0; + chart.update(); + + for (var x = 0; x < 5; x++) { + expect(radToNearestDegree(chart.scales.r.getIndexAngle(x))).toBe((slice * x)); + } + }); + + it('should correctly get the correct label alignment for all points', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + callback: function(value, index) { + return index.toString(); + } + }, + ticks: { + display: false + } + } + } + } + }); + + var scale = chart.scales.r; + + [{ + startAngle: 30, + textAlign: ['right', 'right', 'left', 'left', 'left'], + y: [82, 366, 506, 319, 53] + }, { + startAngle: -30, + textAlign: ['right', 'right', 'left', 'left', 'right'], + y: [319, 506, 366, 82, 53] + }, { + startAngle: 750, + textAlign: ['right', 'right', 'left', 'left', 'left'], + y: [82, 366, 506, 319, 53] + }].forEach(function(expected) { + chart.options.startAngle = expected.startAngle; + chart.update(); + + scale.ctx = window.createMockContext(); + chart.draw(); + + scale.ctx.getCalls().filter(function(x) { + return x.name === 'setTextAlign'; + }).forEach(function(x, i) { + expect(x.args[0]).withContext('startAngle: ' + expected.startAngle + ', tick: ' + i).toBe(expected.textAlign[i]); + }); + + scale.ctx.getCalls().filter(function(x) { + return x.name === 'fillText'; + }).map(function(x, i) { + expect(x.args[2]).toBeCloseToPixel(expected.y[i]); + }); + }); + }); }); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 57add916005..40f790ff8a0 100644 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -1,1238 +1,1238 @@ // Time scale tests describe('Time scale tests', function() { - describe('auto', jasmine.fixture.specs('scale.time')); - - function createScale(data, options, dimensions) { - var width = (dimensions && dimensions.width) || 400; - var height = (dimensions && dimensions.height) || 50; - - options = options || {}; - options.type = 'time'; - options.id = 'xScale0'; - - var chart = window.acquireChart({ - type: 'line', - data: data, - options: { - scales: { - x: options - } - } - }, {canvas: {width: width, height: height}}); - - - return chart.scales.x; - } - - function getLabels(scale) { - return scale.ticks.map(t => t.label); - } - - beforeEach(function() { - // Need a time matcher for getValueFromPixel - jasmine.addMatchers({ - toBeCloseToTime: function() { - return { - compare: function(time, expected) { - var result = false; - var actual = moment(time); - var diff = actual.diff(expected.value, expected.unit, true); - result = Math.abs(diff) < (expected.threshold !== undefined ? expected.threshold : 0.01); - - return { - pass: result - }; - } - }; - } - }); - }); - - it('should load moment.js as a dependency', function() { - expect(window.moment).not.toBe(undefined); - }); - - it('should register the constructor with the registry', function() { - var Constructor = Chart.registry.getScale('time'); - expect(Constructor).not.toBe(undefined); - expect(typeof Constructor).toBe('function'); - }); - - it('should have the correct default config', function() { - var defaultConfig = Chart.defaults.scales.time; - expect(defaultConfig).toEqual({ - bounds: 'data', - adapters: {}, - time: { - parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - isoWeekday: false, // override week start day - minUnit: 'millisecond', - displayFormats: {} - }, - ticks: { - source: 'auto', - major: { - enabled: false - } - } - }); - }); - - it('should correctly determine the unit', function() { - var date = moment('Jan 01 1990', 'MMM DD YYYY'); - var data = []; - for (var i = 0; i < 60; i++) { - data.push({x: date.valueOf(), y: Math.random()}); - date = date.clone().add(1, 'month'); - } - - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: data - }], - }, - options: { - scales: { - x: { - type: 'time', - ticks: { - source: 'data', - autoSkip: true - } - }, - } - } - }); - - var scale = chart.scales.x; - - expect(scale._unit).toEqual('month'); - }); - - describe('when specifying limits', function() { - var mockData = { - labels: ['2015-01-01T20:00:00', '2015-01-02T20:00:00', '2015-01-03T20:00:00'], - }; - - var config; - beforeEach(function() { - config = Chart.helpers.clone(Chart.defaults.scales.time); - config.ticks.source = 'labels'; - config.time.unit = 'day'; - }); - - it('should use the min option when less than first label for building ticks', function() { - config.min = '2014-12-29T04:00:00'; - - var labels = getLabels(createScale(mockData, config)); - expect(labels[0]).toEqual('Jan 1'); - }); - - it('should use the min option when greater than first label for building ticks', function() { - config.min = '2015-01-02T04:00:00'; - - var labels = getLabels(createScale(mockData, config)); - expect(labels[0]).toEqual('Jan 2'); - }); - - it('should use the max option when greater than last label for building ticks', function() { - config.max = '2015-01-05T06:00:00'; - - var labels = getLabels(createScale(mockData, config)); - expect(labels[labels.length - 1]).toEqual('Jan 3'); - }); - - it('should use the max option when less than last label for building ticks', function() { - config.max = '2015-01-02T23:00:00'; - - var labels = getLabels(createScale(mockData, config)); - expect(labels[labels.length - 1]).toEqual('Jan 2'); - }); - }); - - it('should use the isoWeekday option', function() { - var mockData = { - labels: [ - '2015-01-01T20:00:00', // Thursday - '2015-01-02T20:00:00', // Friday - '2015-01-03T20:00:00' // Saturday - ] - }; - - var config = Chart.helpers.mergeIf({ - bounds: 'ticks', - time: { - unit: 'week', - isoWeekday: 3 // Wednesday - } - }, Chart.defaults.scales.time); - - var scale = createScale(mockData, config); - var ticks = getLabels(scale); - - expect(ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']); - }); - - describe('when rendering several days', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: [] - }], - labels: [ - '2015-01-01T20:00:00', - '2015-01-02T21:00:00', - '2015-01-03T22:00:00', - '2015-01-05T23:00:00', - '2015-01-07T03:00', - '2015-01-08T10:00', - '2015-01-10T12:00' - ] - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom' - }, - } - } - }); - - this.scale = this.chart.scales.x; - }); - - it('should be bounded by the nearest week beginnings', function() { - var chart = this.chart; - var scale = this.scale; - expect(scale.getValueForPixel(scale.left)).toBeGreaterThan(moment(chart.data.labels[0]).startOf('week')); - expect(scale.getValueForPixel(scale.right)).toBeLessThan(moment(chart.data.labels[chart.data.labels.length - 1]).add(1, 'week').endOf('week')); - }); - - it('should convert between screen coordinates and times', function() { - var chart = this.chart; - var scale = this.scale; - var timeRange = moment(scale.max).valueOf() - moment(scale.min).valueOf(); - var msPerPix = timeRange / scale.width; - var firstPointOffsetMs = moment(chart.config.data.labels[0]).valueOf() - scale.min; - var firstPointPixel = scale.left + firstPointOffsetMs / msPerPix; - var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - scale.min; - var lastPointPixel = scale.left + lastPointOffsetMs / msPerPix; - - expect(scale.getPixelForValue(moment('2015-01-01T20:00:00').valueOf())).toBeCloseToPixel(firstPointPixel); - expect(scale.getPixelForValue(moment(chart.data.labels[0]).valueOf())).toBeCloseToPixel(firstPointPixel); - expect(scale.getValueForPixel(firstPointPixel)).toBeCloseToTime({ - value: moment(chart.data.labels[0]), - unit: 'hour', - }); - - expect(scale.getPixelForValue(moment('2015-01-10T12:00').valueOf())).toBeCloseToPixel(lastPointPixel); - expect(scale.getValueForPixel(lastPointPixel)).toBeCloseToTime({ - value: moment(chart.data.labels[6]), - unit: 'hour' - }); - }); - }); - - describe('when rendering several years', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2005-07-04', '2017-01-20'], - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'ticks', - position: 'bottom' - }, - } - } - }, {canvas: {width: 800, height: 200}}); - - this.scale = this.chart.scales.x; - }); - - it('should be bounded by nearest step\'s year start and end', function() { - var scale = this.scale; - var ticks = scale.getTicks(); - var step = ticks[1].value - ticks[0].value; - var stepsAmount = Math.floor((scale.max - scale.min) / step); - - expect(scale.getValueForPixel(scale.left)).toBeCloseToTime({ - value: moment(scale.min).startOf('year'), - unit: 'hour', - }); - expect(scale.getValueForPixel(scale.right)).toBeCloseToTime({ - value: moment(scale.min + step * stepsAmount).endOf('year'), - unit: 'hour', - }); - }); - - it('should build the correct ticks', function() { - expect(getLabels(this.scale)).toEqual(['2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018']); - }); - - it('should have ticks with accurate labels', function() { - var scale = this.scale; - var ticks = scale.getTicks(); - // pixelsPerTick is an aproximation which assumes same number of milliseconds per year (not true) - // we use a threshold of 1 day so that we still match these values - var pixelsPerTick = scale.width / (ticks.length - 1); - - for (var i = 0; i < ticks.length - 1; i++) { - var offset = pixelsPerTick * i; - expect(scale.getValueForPixel(scale.left + offset)).toBeCloseToTime({ - value: moment(ticks[i].label + '-01-01'), - unit: 'day', - threshold: 1, - }); - } - }); - }); - - it('should get the correct label for a data value', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: [null, 10, 3] - }], - labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'], // days - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom', - ticks: { - source: 'labels', - autoSkip: false - } - } - } - } - }); - - var xScale = chart.scales.x; - var controller = chart.getDatasetMeta(0).controller; - expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBeTruthy(); - expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBe('Jan 1, 2015, 8:00:00 pm'); - expect(xScale.getLabelForValue(xScale.getValueForPixel(xScale.getPixelForTick(6)))).toBe('Jan 10, 2015, 12:00:00 pm'); - }); - - describe('when ticks.callback is specified', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: [0, 0] - }], - labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00'] - }, - options: { - scales: { - x: { - type: 'time', - time: { - displayFormats: { - second: 'h:mm:ss' - } - }, - ticks: { - callback: function(value) { - return '<' + value + '>'; - } - } - } - } - } - }); - this.scale = this.chart.scales.x; - }); - - it('should get the correct labels for ticks', function() { - var labels = getLabels(this.scale); - - expect(labels.length).toEqual(21); - expect(labels[0]).toEqual('<8:00:00>'); - expect(labels[labels.length - 1]).toEqual('<8:01:00>'); - }); - - it('should update ticks.callback correctly', function() { - var chart = this.chart; - chart.options.scales.x.ticks.callback = function(value) { - return '{' + value + '}'; - }; - chart.update(); - - var labels = getLabels(this.scale); - expect(labels.length).toEqual(21); - expect(labels[0]).toEqual('{8:00:00}'); - expect(labels[labels.length - 1]).toEqual('{8:01:00}'); - }); - }); - - it('should get the correct label when time is specified as a string', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: [{x: '2015-01-01T20:00:00', y: 10}, {x: '2015-01-02T21:00:00', y: 3}] - }], - }, - options: { - scales: { - x: { - type: 'time', - position: 'bottom' - }, - } - } - }); - - var xScale = chart.scales.x; - var controller = chart.getDatasetMeta(0).controller; - var value = controller.getParsed(0)[xScale.id]; - expect(xScale.getLabelForValue(value)).toBeTruthy(); - expect(xScale.getLabelForValue(value)).toBe('Jan 1, 2015, 8:00:00 pm'); - }); - - it('should round to isoWeekday', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [{x: '2020-04-12T20:00:00', y: 1}, {x: '2020-04-13T20:00:00', y: 2}] - }] - }, - options: { - scales: { - x: { - type: 'time', - ticks: { - source: 'data' - }, - time: { - unit: 'week', - round: 'week', - isoWeekday: 1, - displayFormats: { - week: 'WW' - } - } - }, - } - } - }); - - expect(getLabels(chart.scales.x)).toEqual(['15', '16']); - }); - - it('should get the correct label for a timestamp', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - xAxisID: 'x', - data: [ - {t: +new Date('2018-01-08 05:14:23.234'), y: 10}, - {t: +new Date('2018-01-09 06:17:43.426'), y: 3} - ] - }], - }, - options: { - parsing: {xAxisKey: 't'}, - scales: { - x: { - type: 'time', - position: 'bottom' - }, - } - } - }); - - var xScale = chart.scales.x; - var controller = chart.getDatasetMeta(0).controller; - var label = xScale.getLabelForValue(controller.getParsed(0)[xScale.id]); - expect(label).toEqual('Jan 8, 2018, 5:14:23 am'); - }); - - it('should get the correct pixel for only one data in the dataset', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2016-05-27'], - datasets: [{ - xAxisID: 'x', - data: [5] - }] - }, - options: { - scales: { - x: { - display: true, - type: 'time' - } - } - } - }); - - var xScale = chart.scales.x; - var pixel = xScale.getPixelForValue(moment('2016-05-27').valueOf()); - - expect(xScale.getValueForPixel(pixel)).toEqual(moment(chart.data.labels[0]).valueOf()); - }); - - it('does not create a negative width chart when hidden', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - datasets: [{ - data: [] - }] - }, - options: { - scales: { - x: { - type: 'time', - ticks: { - min: moment().subtract(1, 'months'), - max: moment(), - } - }, - }, - responsive: true, - }, - }, { - wrapper: { - style: 'display: none', - }, - }); - expect(chart.scales.y.width).toEqual(0); - expect(chart.scales.y.maxWidth).toEqual(0); - expect(chart.width).toEqual(0); - }); - - describe('when ticks.source', function() { - describe('is "labels"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY' - }, - ticks: { - source: 'labels' - } - } - } - } - }); - }); - - it ('should generate ticks from "data.labels"', function() { - var scale = this.chart.scales.x; - - expect(scale.min).toEqual(+moment('2017', 'YYYY')); - expect(scale.max).toEqual(+moment('2042', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2019', '2020', '2025', '2042']); - }); - it ('should not add ticks for min and max if they extend the labels range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2051'; - chart.update(); - - expect(scale.min).toEqual(+moment('2012', 'YYYY')); - expect(scale.max).toEqual(+moment('2051', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2019', '2020', '2025', '2042']); - }); - it ('should not duplicate ticks if min and max are the labels limits', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2017'; - options.max = '2042'; - chart.update(); - - expect(scale.min).toEqual(+moment('2017', 'YYYY')); - expect(scale.max).toEqual(+moment('2042', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2019', '2020', '2025', '2042']); - }); - it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() { - var chart = this.chart; - var scale = chart.scales.x; - - chart.data.labels = []; - chart.update(); - - expect(scale.min).toEqual(+moment().startOf('day')); - expect(scale.max).toEqual(+moment().endOf('day') + 1); - expect(getLabels(scale)).toEqual([]); - }); - it ('should correctly handle empty `data.labels` using `time.unit`', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.time.unit = 'year'; - chart.data.labels = []; - chart.update(); - - expect(scale.min).toEqual(+moment().startOf('year')); - expect(scale.max).toEqual(+moment().endOf('year') + 1); - expect(getLabels(scale)).toEqual([]); - }); - }); - - describe('is "data"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [ - {data: [0, 1, 2, 3, 4, 5]}, - {data: [ - {x: '2018', y: 6}, - {x: '2020', y: 7}, - {x: '2043', y: 8} - ]} - ] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY' - }, - ticks: { - source: 'data' - } - } - } - } - }); - }); - - it ('should generate ticks from "datasets.data"', function() { - var scale = this.chart.scales.x; - - expect(scale.min).toEqual(+moment('2017', 'YYYY')); - expect(scale.max).toEqual(+moment('2043', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2018', '2019', '2020', '2025', '2042', '2043']); - }); - it ('should not add ticks for min and max if they extend the labels range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2051'; - chart.update(); - - expect(scale.min).toEqual(+moment('2012', 'YYYY')); - expect(scale.max).toEqual(+moment('2051', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2018', '2019', '2020', '2025', '2042', '2043']); - }); - it ('should not duplicate ticks if min and max are the labels limits', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2017'; - options.max = '2043'; - chart.update(); - - expect(scale.min).toEqual(+moment('2017', 'YYYY')); - expect(scale.max).toEqual(+moment('2043', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2017', '2018', '2019', '2020', '2025', '2042', '2043']); - }); - it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() { - var chart = this.chart; - var scale = chart.scales.x; - - chart.data.labels = []; - chart.update(); - - expect(scale.min).toEqual(+moment('2018', 'YYYY')); - expect(scale.max).toEqual(+moment('2043', 'YYYY')); - expect(getLabels(scale)).toEqual([ - '2018', '2020', '2043']); - }); - it ('should correctly handle empty `data.labels` and hidden datasets using `time.unit`', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.time.unit = 'year'; - chart.data.labels = []; - var meta = chart.getDatasetMeta(1); - meta.hidden = true; - chart.update(); - - expect(scale.min).toEqual(+moment().startOf('year')); - expect(scale.max).toEqual(+moment().endOf('year') + 1); - expect(getLabels(scale)).toEqual([]); - }); - }); - }); - - [true, false].forEach(function(normalized) { - describe('when normalized is ' + normalized + ' and scale type', function() { - describe('is "timeseries"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4]}] - }, - options: { - normalized, - scales: { - x: { - type: 'timeseries', - time: { - parser: 'YYYY' - }, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }); - }); - - it ('should space data out with the same gap, whatever their time values', function() { - var scale = this.chart.scales.x; - var start = scale.left; - var slice = scale.width / 4; - - expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice); - expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * 2); - expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * 3); - expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4); - }); - it ('should add a step before if scale.min is before the first data', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - chart.update(); - - var start = scale.left; - var slice = scale.width / 5; - - expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice); - expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5); - }); - it ('should add a step after if scale.max is after the last data', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.max = '2050'; - chart.update(); - - var start = scale.left; - var slice = scale.width / 5; - - expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4); - }); - it ('should add steps before and after if scale.min/max are outside the data range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2050'; - chart.update(); - - var start = scale.left; - var slice = scale.width / 6; - - expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice); - expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5); - }); - }); - describe('is "time"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - type: 'time', - time: { - parser: 'YYYY' - }, - ticks: { - source: 'labels' - } - }, - y: { - display: false - } - } - } - }); - }); - - it ('should space data out with a gap relative to their time values', function() { - var scale = this.chart.scales.x; - var start = scale.left; - var slice = scale.width / (2042 - 2017); - - expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); - expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2017)); - expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2017)); - expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2017)); - expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2017)); - }); - it ('should take in account scale min and max if outside the ticks range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2050'; - chart.update(); - - var start = scale.left; - var slice = scale.width / (2050 - 2012); - - expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start + slice * (2017 - 2012)); - expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2012)); - expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2012)); - expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2012)); - expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2012)); - }); - }); - }); - }); - - describe('when bounds', function() { - describe('is "data"', function() { - it ('should preserve the data range', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'data', - time: { - parser: 'MM/DD HH:mm', - unit: 'day' - } - }, - y: { - display: false - } - } - } - }); - - var scale = chart.scales.x; - - expect(scale.min).toEqual(+moment('02/20 08:00', 'MM/DD HH:mm')); - expect(scale.max).toEqual(+moment('02/23 11:00', 'MM/DD HH:mm')); - expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left); - expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left + scale.width); - expect(getLabels(scale)).toEqual([ - 'Feb 21', 'Feb 22', 'Feb 23']); - }); - }); - - describe('is "labels"', function() { - it('should preserve the label range', function() { - var chart = window.acquireChart({ - type: 'line', - data: { - labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - type: 'time', - bounds: 'ticks', - time: { - parser: 'MM/DD HH:mm', - unit: 'day' - } - }, - y: { - display: false - } - } - } - }); - - var scale = chart.scales.x; - var ticks = scale.getTicks(); - - expect(scale.min).toEqual(ticks[0].value); - expect(scale.max).toEqual(ticks[ticks.length - 1].value); - expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(60); - expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(426); - expect(getLabels(scale)).toEqual([ - 'Feb 20', 'Feb 21', 'Feb 22', 'Feb 23', 'Feb 24']); - }); - }); - }); - - describe('when min and/or max are defined', function() { - ['auto', 'data', 'labels'].forEach(function(source) { - ['data', 'ticks'].forEach(function(bounds) { - describe('and ticks.source is "' + source + '" and bounds "' + bounds + '"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - type: 'time', - bounds: bounds, - time: { - parser: 'MM/DD HH:mm', - unit: 'day' - }, - ticks: { - source: source - } - }, - y: { - display: false - } - } - } - }); - }); - - it ('should expand scale to the min/max range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - var min = '02/19 07:00'; - var max = '02/24 08:00'; - var minMillis = +moment(min, 'MM/DD HH:mm'); - var maxMillis = +moment(max, 'MM/DD HH:mm'); - - options.min = min; - options.max = max; - chart.update(); - - expect(scale.min).toEqual(minMillis); - expect(scale.max).toEqual(maxMillis); - expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left); - expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width); - scale.getTicks().forEach(function(tick) { - expect(tick.value >= minMillis).toBeTruthy(); - expect(tick.value <= maxMillis).toBeTruthy(); - }); - }); - it ('should shrink scale to the min/max range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - var min = '02/21 07:00'; - var max = '02/22 20:00'; - var minMillis = +moment(min, 'MM/DD HH:mm'); - var maxMillis = +moment(max, 'MM/DD HH:mm'); - - options.min = min; - options.max = max; - chart.update(); - - expect(scale.min).toEqual(minMillis); - expect(scale.max).toEqual(maxMillis); - expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left); - expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width); - scale.getTicks().forEach(function(tick) { - expect(tick.value >= minMillis).toBeTruthy(); - expect(tick.value <= maxMillis).toBeTruthy(); - }); - }); - }); - }); - }); - }); - - ['auto', 'data', 'labels'].forEach(function(source) { - ['timeseries', 'time'].forEach(function(type) { - describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2018', '2019', '2020', '2021'], - datasets: [{data: [0, 1, 2, 3, 4]}] - }, - options: { - scales: { - x: { - type: type, - time: { - parser: 'YYYY', - unit: 'year' - }, - ticks: { - source: source - } - } - } - } - }); - }); - - it ('should not add offset from the edges', function() { - var scale = this.chart.scales.x; - - expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left); - expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width); - }); - - it ('should add offset from the edges if offset is true', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.offset = true; - chart.update(); - - var numTicks = scale.ticks.length; - var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); - var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2); - - expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2); - expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2); - }); - - it ('should not add offset if min and max extend the labels range', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2051'; - chart.update(); - - expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left); - expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width); - }); - }); - }); - }); - - it ('should handle offset when there are more data points than ticks', function() { - const chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [{ - data: [{x: 631180800000, y: '31.84'}, {x: 631267200000, y: '30.89'}, {x: 631353600000, y: '33.00'}, {x: 631440000000, y: '33.52'}, {x: 631526400000, y: '32.24'}, {x: 631785600000, y: '32.74'}, {x: 631872000000, y: '31.45'}, {x: 631958400000, y: '32.60'}, {x: 632044800000, y: '31.77'}, {x: 632131200000, y: '32.45'}, {x: 632390400000, y: '31.13'}, {x: 632476800000, y: '31.82'}, {x: 632563200000, y: '30.81'}, {x: 632649600000, y: '30.07'}, {x: 632736000000, y: '29.31'}, {x: 632995200000, y: '29.82'}, {x: 633081600000, y: '30.20'}, {x: 633168000000, y: '30.78'}, {x: 633254400000, y: '30.72'}, {x: 633340800000, y: '31.62'}, {x: 633600000000, y: '30.64'}, {x: 633686400000, y: '32.36'}, {x: 633772800000, y: '34.66'}, {x: 633859200000, y: '33.96'}, {x: 633945600000, y: '34.20'}, {x: 634204800000, y: '32.20'}, {x: 634291200000, y: '32.44'}, {x: 634377600000, y: '32.72'}, {x: 634464000000, y: '32.95'}, {x: 634550400000, y: '32.95'}, {x: 634809600000, y: '30.88'}, {x: 634896000000, y: '29.44'}, {x: 634982400000, y: '29.36'}, {x: 635068800000, y: '28.84'}, {x: 635155200000, y: '30.85'}, {x: 635414400000, y: '32.00'}, {x: 635500800000, y: '32.74'}, {x: 635587200000, y: '33.16'}, {x: 635673600000, y: '34.73'}, {x: 635760000000, y: '32.89'}, {x: 636019200000, y: '32.41'}, {x: 636105600000, y: '31.15'}, {x: 636192000000, y: '30.63'}, {x: 636278400000, y: '29.60'}, {x: 636364800000, y: '29.31'}, {x: 636624000000, y: '29.83'}, {x: 636710400000, y: '27.97'}, {x: 636796800000, y: '26.18'}, {x: 636883200000, y: '26.06'}, {x: 636969600000, y: '26.34'}, {x: 637228800000, y: '27.75'}, {x: 637315200000, y: '29.05'}, {x: 637401600000, y: '28.82'}, {x: 637488000000, y: '29.43'}, {x: 637574400000, y: '29.53'}, {x: 637833600000, y: '28.50'}, {x: 637920000000, y: '28.87'}, {x: 638006400000, y: '28.11'}, {x: 638092800000, y: '27.79'}, {x: 638179200000, y: '28.18'}, {x: 638438400000, y: '28.27'}, {x: 638524800000, y: '28.29'}, {x: 638611200000, y: '29.63'}, {x: 638697600000, y: '29.13'}, {x: 638784000000, y: '26.57'}, {x: 639039600000, y: '27.19'}, {x: 639126000000, y: '27.48'}, {x: 639212400000, y: '27.79'}, {x: 639298800000, y: '28.48'}, {x: 639385200000, y: '27.88'}, {x: 639644400000, y: '25.63'}, {x: 639730800000, y: '25.02'}, {x: 639817200000, y: '25.26'}, {x: 639903600000, y: '25.00'}, {x: 639990000000, y: '26.23'}, {x: 640249200000, y: '26.22'}, {x: 640335600000, y: '26.36'}, {x: 640422000000, y: '25.45'}, {x: 640508400000, y: '24.62'}, {x: 640594800000, y: '26.65'}, {x: 640854000000, y: '26.28'}, {x: 640940400000, y: '27.25'}, {x: 641026800000, y: '25.93'}], - backgroundColor: '#ff6666' - }] - }, - options: { - scales: { - x: { - type: 'timeseries', - offset: true, - ticks: { - source: 'data', - autoSkip: true, - maxRotation: 0 - } - }, - y: { - type: 'linear', - gridLines: { - drawBorder: false - } - } - } - }, - plugins: { - legend: false - } - }); - const scale = chart.scales.x; - expect(scale.getPixelForDecimal(0)).toBeCloseToPixel(29); - expect(scale.getPixelForDecimal(1.0)).toBeCloseToPixel(494); - }); - - ['data', 'labels'].forEach(function(source) { - ['timeseries', 'time'].forEach(function(type) { - describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() { - beforeEach(function() { - this.chart = window.acquireChart({ - type: 'line', - data: { - labels: ['2017', '2019', '2020', '2025', '2042'], - datasets: [{data: [0, 1, 2, 3, 4, 5]}] - }, - options: { - scales: { - x: { - id: 'x', - type: type, - time: { - parser: 'YYYY' - }, - ticks: { - source: source - } - } - } - } - }); - }); - - it ('should add offset if min and max extend the labels range and offset is true', function() { - var chart = this.chart; - var scale = chart.scales.x; - var options = chart.options.scales.x; - - options.min = '2012'; - options.max = '2051'; - options.offset = true; - chart.update(); - - var numTicks = scale.ticks.length; - var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); - var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2); - expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2); - expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2); - }); - }); - }); - }); - - describe('Deprecations', function() { - describe('options.time.displayFormats', function() { - it('should generate defaults from adapter presets', function() { - var chart = window.acquireChart({ - type: 'line', - data: {}, - options: { - scales: { - x: { - type: 'time' - } - } - } - }); - - // NOTE: built-in adapter uses moment - var expected = { - datetime: 'MMM D, YYYY, h:mm:ss a', - millisecond: 'h:mm:ss.SSS a', - second: 'h:mm:ss a', - minute: 'h:mm a', - hour: 'hA', - day: 'MMM D', - week: 'll', - month: 'MMM YYYY', - quarter: '[Q]Q - YYYY', - year: 'YYYY' - }; - - expect(chart.scales.x.options.time.displayFormats).toEqual(expected); - expect(chart.options.scales.x.time.displayFormats).toEqual(expected); - }); - - it('should merge user formats with adapter presets', function() { - var chart = window.acquireChart({ - type: 'line', - data: {}, - options: { - scales: { - x: { - type: 'time', - time: { - displayFormats: { - millisecond: 'foo', - hour: 'bar', - month: 'bla' - } - } - } - } - } - }); - - // NOTE: built-in adapter uses moment - var expected = { - datetime: 'MMM D, YYYY, h:mm:ss a', - millisecond: 'foo', - second: 'h:mm:ss a', - minute: 'h:mm a', - hour: 'bar', - day: 'MMM D', - week: 'll', - month: 'bla', - quarter: '[Q]Q - YYYY', - year: 'YYYY' - }; - - expect(chart.scales.x.options.time.displayFormats).toEqual(expected); - expect(chart.options.scales.x.time.displayFormats).toEqual(expected); - }); - }); - }); + describe('auto', jasmine.fixture.specs('scale.time')); + + function createScale(data, options, dimensions) { + var width = (dimensions && dimensions.width) || 400; + var height = (dimensions && dimensions.height) || 50; + + options = options || {}; + options.type = 'time'; + options.id = 'xScale0'; + + var chart = window.acquireChart({ + type: 'line', + data: data, + options: { + scales: { + x: options + } + } + }, {canvas: {width: width, height: height}}); + + + return chart.scales.x; + } + + function getLabels(scale) { + return scale.ticks.map(t => t.label); + } + + beforeEach(function() { + // Need a time matcher for getValueFromPixel + jasmine.addMatchers({ + toBeCloseToTime: function() { + return { + compare: function(time, expected) { + var result = false; + var actual = moment(time); + var diff = actual.diff(expected.value, expected.unit, true); + result = Math.abs(diff) < (expected.threshold !== undefined ? expected.threshold : 0.01); + + return { + pass: result + }; + } + }; + } + }); + }); + + it('should load moment.js as a dependency', function() { + expect(window.moment).not.toBe(undefined); + }); + + it('should register the constructor with the registry', function() { + var Constructor = Chart.registry.getScale('time'); + expect(Constructor).not.toBe(undefined); + expect(typeof Constructor).toBe('function'); + }); + + it('should have the correct default config', function() { + var defaultConfig = Chart.defaults.scales.time; + expect(defaultConfig).toEqual({ + bounds: 'data', + adapters: {}, + time: { + parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + isoWeekday: false, // override week start day + minUnit: 'millisecond', + displayFormats: {} + }, + ticks: { + source: 'auto', + major: { + enabled: false + } + } + }); + }); + + it('should correctly determine the unit', function() { + var date = moment('Jan 01 1990', 'MMM DD YYYY'); + var data = []; + for (var i = 0; i < 60; i++) { + data.push({x: date.valueOf(), y: Math.random()}); + date = date.clone().add(1, 'month'); + } + + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: data + }], + }, + options: { + scales: { + x: { + type: 'time', + ticks: { + source: 'data', + autoSkip: true + } + }, + } + } + }); + + var scale = chart.scales.x; + + expect(scale._unit).toEqual('month'); + }); + + describe('when specifying limits', function() { + var mockData = { + labels: ['2015-01-01T20:00:00', '2015-01-02T20:00:00', '2015-01-03T20:00:00'], + }; + + var config; + beforeEach(function() { + config = Chart.helpers.clone(Chart.defaults.scales.time); + config.ticks.source = 'labels'; + config.time.unit = 'day'; + }); + + it('should use the min option when less than first label for building ticks', function() { + config.min = '2014-12-29T04:00:00'; + + var labels = getLabels(createScale(mockData, config)); + expect(labels[0]).toEqual('Jan 1'); + }); + + it('should use the min option when greater than first label for building ticks', function() { + config.min = '2015-01-02T04:00:00'; + + var labels = getLabels(createScale(mockData, config)); + expect(labels[0]).toEqual('Jan 2'); + }); + + it('should use the max option when greater than last label for building ticks', function() { + config.max = '2015-01-05T06:00:00'; + + var labels = getLabels(createScale(mockData, config)); + expect(labels[labels.length - 1]).toEqual('Jan 3'); + }); + + it('should use the max option when less than last label for building ticks', function() { + config.max = '2015-01-02T23:00:00'; + + var labels = getLabels(createScale(mockData, config)); + expect(labels[labels.length - 1]).toEqual('Jan 2'); + }); + }); + + it('should use the isoWeekday option', function() { + var mockData = { + labels: [ + '2015-01-01T20:00:00', // Thursday + '2015-01-02T20:00:00', // Friday + '2015-01-03T20:00:00' // Saturday + ] + }; + + var config = Chart.helpers.mergeIf({ + bounds: 'ticks', + time: { + unit: 'week', + isoWeekday: 3 // Wednesday + } + }, Chart.defaults.scales.time); + + var scale = createScale(mockData, config); + var ticks = getLabels(scale); + + expect(ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']); + }); + + describe('when rendering several days', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: [] + }], + labels: [ + '2015-01-01T20:00:00', + '2015-01-02T21:00:00', + '2015-01-03T22:00:00', + '2015-01-05T23:00:00', + '2015-01-07T03:00', + '2015-01-08T10:00', + '2015-01-10T12:00' + ] + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom' + }, + } + } + }); + + this.scale = this.chart.scales.x; + }); + + it('should be bounded by the nearest week beginnings', function() { + var chart = this.chart; + var scale = this.scale; + expect(scale.getValueForPixel(scale.left)).toBeGreaterThan(moment(chart.data.labels[0]).startOf('week')); + expect(scale.getValueForPixel(scale.right)).toBeLessThan(moment(chart.data.labels[chart.data.labels.length - 1]).add(1, 'week').endOf('week')); + }); + + it('should convert between screen coordinates and times', function() { + var chart = this.chart; + var scale = this.scale; + var timeRange = moment(scale.max).valueOf() - moment(scale.min).valueOf(); + var msPerPix = timeRange / scale.width; + var firstPointOffsetMs = moment(chart.config.data.labels[0]).valueOf() - scale.min; + var firstPointPixel = scale.left + firstPointOffsetMs / msPerPix; + var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - scale.min; + var lastPointPixel = scale.left + lastPointOffsetMs / msPerPix; + + expect(scale.getPixelForValue(moment('2015-01-01T20:00:00').valueOf())).toBeCloseToPixel(firstPointPixel); + expect(scale.getPixelForValue(moment(chart.data.labels[0]).valueOf())).toBeCloseToPixel(firstPointPixel); + expect(scale.getValueForPixel(firstPointPixel)).toBeCloseToTime({ + value: moment(chart.data.labels[0]), + unit: 'hour', + }); + + expect(scale.getPixelForValue(moment('2015-01-10T12:00').valueOf())).toBeCloseToPixel(lastPointPixel); + expect(scale.getValueForPixel(lastPointPixel)).toBeCloseToTime({ + value: moment(chart.data.labels[6]), + unit: 'hour' + }); + }); + }); + + describe('when rendering several years', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2005-07-04', '2017-01-20'], + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'ticks', + position: 'bottom' + }, + } + } + }, {canvas: {width: 800, height: 200}}); + + this.scale = this.chart.scales.x; + }); + + it('should be bounded by nearest step\'s year start and end', function() { + var scale = this.scale; + var ticks = scale.getTicks(); + var step = ticks[1].value - ticks[0].value; + var stepsAmount = Math.floor((scale.max - scale.min) / step); + + expect(scale.getValueForPixel(scale.left)).toBeCloseToTime({ + value: moment(scale.min).startOf('year'), + unit: 'hour', + }); + expect(scale.getValueForPixel(scale.right)).toBeCloseToTime({ + value: moment(scale.min + step * stepsAmount).endOf('year'), + unit: 'hour', + }); + }); + + it('should build the correct ticks', function() { + expect(getLabels(this.scale)).toEqual(['2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018']); + }); + + it('should have ticks with accurate labels', function() { + var scale = this.scale; + var ticks = scale.getTicks(); + // pixelsPerTick is an aproximation which assumes same number of milliseconds per year (not true) + // we use a threshold of 1 day so that we still match these values + var pixelsPerTick = scale.width / (ticks.length - 1); + + for (var i = 0; i < ticks.length - 1; i++) { + var offset = pixelsPerTick * i; + expect(scale.getValueForPixel(scale.left + offset)).toBeCloseToTime({ + value: moment(ticks[i].label + '-01-01'), + unit: 'day', + threshold: 1, + }); + } + }); + }); + + it('should get the correct label for a data value', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: [null, 10, 3] + }], + labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'], // days + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom', + ticks: { + source: 'labels', + autoSkip: false + } + } + } + } + }); + + var xScale = chart.scales.x; + var controller = chart.getDatasetMeta(0).controller; + expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBeTruthy(); + expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBe('Jan 1, 2015, 8:00:00 pm'); + expect(xScale.getLabelForValue(xScale.getValueForPixel(xScale.getPixelForTick(6)))).toBe('Jan 10, 2015, 12:00:00 pm'); + }); + + describe('when ticks.callback is specified', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: [0, 0] + }], + labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00'] + }, + options: { + scales: { + x: { + type: 'time', + time: { + displayFormats: { + second: 'h:mm:ss' + } + }, + ticks: { + callback: function(value) { + return '<' + value + '>'; + } + } + } + } + } + }); + this.scale = this.chart.scales.x; + }); + + it('should get the correct labels for ticks', function() { + var labels = getLabels(this.scale); + + expect(labels.length).toEqual(21); + expect(labels[0]).toEqual('<8:00:00>'); + expect(labels[labels.length - 1]).toEqual('<8:01:00>'); + }); + + it('should update ticks.callback correctly', function() { + var chart = this.chart; + chart.options.scales.x.ticks.callback = function(value) { + return '{' + value + '}'; + }; + chart.update(); + + var labels = getLabels(this.scale); + expect(labels.length).toEqual(21); + expect(labels[0]).toEqual('{8:00:00}'); + expect(labels[labels.length - 1]).toEqual('{8:01:00}'); + }); + }); + + it('should get the correct label when time is specified as a string', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: [{x: '2015-01-01T20:00:00', y: 10}, {x: '2015-01-02T21:00:00', y: 3}] + }], + }, + options: { + scales: { + x: { + type: 'time', + position: 'bottom' + }, + } + } + }); + + var xScale = chart.scales.x; + var controller = chart.getDatasetMeta(0).controller; + var value = controller.getParsed(0)[xScale.id]; + expect(xScale.getLabelForValue(value)).toBeTruthy(); + expect(xScale.getLabelForValue(value)).toBe('Jan 1, 2015, 8:00:00 pm'); + }); + + it('should round to isoWeekday', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [{x: '2020-04-12T20:00:00', y: 1}, {x: '2020-04-13T20:00:00', y: 2}] + }] + }, + options: { + scales: { + x: { + type: 'time', + ticks: { + source: 'data' + }, + time: { + unit: 'week', + round: 'week', + isoWeekday: 1, + displayFormats: { + week: 'WW' + } + } + }, + } + } + }); + + expect(getLabels(chart.scales.x)).toEqual(['15', '16']); + }); + + it('should get the correct label for a timestamp', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'x', + data: [ + {t: +new Date('2018-01-08 05:14:23.234'), y: 10}, + {t: +new Date('2018-01-09 06:17:43.426'), y: 3} + ] + }], + }, + options: { + parsing: {xAxisKey: 't'}, + scales: { + x: { + type: 'time', + position: 'bottom' + }, + } + } + }); + + var xScale = chart.scales.x; + var controller = chart.getDatasetMeta(0).controller; + var label = xScale.getLabelForValue(controller.getParsed(0)[xScale.id]); + expect(label).toEqual('Jan 8, 2018, 5:14:23 am'); + }); + + it('should get the correct pixel for only one data in the dataset', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2016-05-27'], + datasets: [{ + xAxisID: 'x', + data: [5] + }] + }, + options: { + scales: { + x: { + display: true, + type: 'time' + } + } + } + }); + + var xScale = chart.scales.x; + var pixel = xScale.getPixelForValue(moment('2016-05-27').valueOf()); + + expect(xScale.getValueForPixel(pixel)).toEqual(moment(chart.data.labels[0]).valueOf()); + }); + + it('does not create a negative width chart when hidden', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }] + }, + options: { + scales: { + x: { + type: 'time', + ticks: { + min: moment().subtract(1, 'months'), + max: moment(), + } + }, + }, + responsive: true, + }, + }, { + wrapper: { + style: 'display: none', + }, + }); + expect(chart.scales.y.width).toEqual(0); + expect(chart.scales.y.maxWidth).toEqual(0); + expect(chart.width).toEqual(0); + }); + + describe('when ticks.source', function() { + describe('is "labels"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + source: 'labels' + } + } + } + } + }); + }); + + it ('should generate ticks from "data.labels"', function() { + var scale = this.chart.scales.x; + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2042', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should not add ticks for min and max if they extend the labels range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2051'; + chart.update(); + + expect(scale.min).toEqual(+moment('2012', 'YYYY')); + expect(scale.max).toEqual(+moment('2051', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should not duplicate ticks if min and max are the labels limits', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2017'; + options.max = '2042'; + chart.update(); + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2042', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() { + var chart = this.chart; + var scale = chart.scales.x; + + chart.data.labels = []; + chart.update(); + + expect(scale.min).toEqual(+moment().startOf('day')); + expect(scale.max).toEqual(+moment().endOf('day') + 1); + expect(getLabels(scale)).toEqual([]); + }); + it ('should correctly handle empty `data.labels` using `time.unit`', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.time.unit = 'year'; + chart.data.labels = []; + chart.update(); + + expect(scale.min).toEqual(+moment().startOf('year')); + expect(scale.max).toEqual(+moment().endOf('year') + 1); + expect(getLabels(scale)).toEqual([]); + }); + }); + + describe('is "data"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [ + {data: [0, 1, 2, 3, 4, 5]}, + {data: [ + {x: '2018', y: 6}, + {x: '2020', y: 7}, + {x: '2043', y: 8} + ]} + ] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + source: 'data' + } + } + } + } + }); + }); + + it ('should generate ticks from "datasets.data"', function() { + var scale = this.chart.scales.x; + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2043', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2018', '2019', '2020', '2025', '2042', '2043']); + }); + it ('should not add ticks for min and max if they extend the labels range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2051'; + chart.update(); + + expect(scale.min).toEqual(+moment('2012', 'YYYY')); + expect(scale.max).toEqual(+moment('2051', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2018', '2019', '2020', '2025', '2042', '2043']); + }); + it ('should not duplicate ticks if min and max are the labels limits', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2017'; + options.max = '2043'; + chart.update(); + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2043', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2017', '2018', '2019', '2020', '2025', '2042', '2043']); + }); + it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() { + var chart = this.chart; + var scale = chart.scales.x; + + chart.data.labels = []; + chart.update(); + + expect(scale.min).toEqual(+moment('2018', 'YYYY')); + expect(scale.max).toEqual(+moment('2043', 'YYYY')); + expect(getLabels(scale)).toEqual([ + '2018', '2020', '2043']); + }); + it ('should correctly handle empty `data.labels` and hidden datasets using `time.unit`', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.time.unit = 'year'; + chart.data.labels = []; + var meta = chart.getDatasetMeta(1); + meta.hidden = true; + chart.update(); + + expect(scale.min).toEqual(+moment().startOf('year')); + expect(scale.max).toEqual(+moment().endOf('year') + 1); + expect(getLabels(scale)).toEqual([]); + }); + }); + }); + + [true, false].forEach(function(normalized) { + describe('when normalized is ' + normalized + ' and scale type', function() { + describe('is "timeseries"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4]}] + }, + options: { + normalized, + scales: { + x: { + type: 'timeseries', + time: { + parser: 'YYYY' + }, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }); + }); + + it ('should space data out with the same gap, whatever their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / 4; + + expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice); + expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * 2); + expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * 3); + expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4); + }); + it ('should add a step before if scale.min is before the first data', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + + expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice); + expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5); + }); + it ('should add a step after if scale.max is after the last data', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + + expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4); + }); + it ('should add steps before and after if scale.min/max are outside the data range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 6; + + expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice); + expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5); + }); + }); + describe('is "time"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + source: 'labels' + } + }, + y: { + display: false + } + } + } + }); + }); + + it ('should space data out with a gap relative to their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / (2042 - 2017); + + expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2017)); + expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2017)); + expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2017)); + expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2017)); + }); + it ('should take in account scale min and max if outside the ticks range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / (2050 - 2012); + + expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start + slice * (2017 - 2012)); + expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2012)); + expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2012)); + expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2012)); + expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2012)); + }); + }); + }); + }); + + describe('when bounds', function() { + describe('is "data"', function() { + it ('should preserve the data range', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'data', + time: { + parser: 'MM/DD HH:mm', + unit: 'day' + } + }, + y: { + display: false + } + } + } + }); + + var scale = chart.scales.x; + + expect(scale.min).toEqual(+moment('02/20 08:00', 'MM/DD HH:mm')); + expect(scale.max).toEqual(+moment('02/23 11:00', 'MM/DD HH:mm')); + expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left); + expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left + scale.width); + expect(getLabels(scale)).toEqual([ + 'Feb 21', 'Feb 22', 'Feb 23']); + }); + }); + + describe('is "labels"', function() { + it('should preserve the label range', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + type: 'time', + bounds: 'ticks', + time: { + parser: 'MM/DD HH:mm', + unit: 'day' + } + }, + y: { + display: false + } + } + } + }); + + var scale = chart.scales.x; + var ticks = scale.getTicks(); + + expect(scale.min).toEqual(ticks[0].value); + expect(scale.max).toEqual(ticks[ticks.length - 1].value); + expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(60); + expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(426); + expect(getLabels(scale)).toEqual([ + 'Feb 20', 'Feb 21', 'Feb 22', 'Feb 23', 'Feb 24']); + }); + }); + }); + + describe('when min and/or max are defined', function() { + ['auto', 'data', 'labels'].forEach(function(source) { + ['data', 'ticks'].forEach(function(bounds) { + describe('and ticks.source is "' + source + '" and bounds "' + bounds + '"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + type: 'time', + bounds: bounds, + time: { + parser: 'MM/DD HH:mm', + unit: 'day' + }, + ticks: { + source: source + } + }, + y: { + display: false + } + } + } + }); + }); + + it ('should expand scale to the min/max range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + var min = '02/19 07:00'; + var max = '02/24 08:00'; + var minMillis = +moment(min, 'MM/DD HH:mm'); + var maxMillis = +moment(max, 'MM/DD HH:mm'); + + options.min = min; + options.max = max; + chart.update(); + + expect(scale.min).toEqual(minMillis); + expect(scale.max).toEqual(maxMillis); + expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left); + expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width); + scale.getTicks().forEach(function(tick) { + expect(tick.value >= minMillis).toBeTruthy(); + expect(tick.value <= maxMillis).toBeTruthy(); + }); + }); + it ('should shrink scale to the min/max range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + var min = '02/21 07:00'; + var max = '02/22 20:00'; + var minMillis = +moment(min, 'MM/DD HH:mm'); + var maxMillis = +moment(max, 'MM/DD HH:mm'); + + options.min = min; + options.max = max; + chart.update(); + + expect(scale.min).toEqual(minMillis); + expect(scale.max).toEqual(maxMillis); + expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left); + expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width); + scale.getTicks().forEach(function(tick) { + expect(tick.value >= minMillis).toBeTruthy(); + expect(tick.value <= maxMillis).toBeTruthy(); + }); + }); + }); + }); + }); + }); + + ['auto', 'data', 'labels'].forEach(function(source) { + ['timeseries', 'time'].forEach(function(type) { + describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2018', '2019', '2020', '2021'], + datasets: [{data: [0, 1, 2, 3, 4]}] + }, + options: { + scales: { + x: { + type: type, + time: { + parser: 'YYYY', + unit: 'year' + }, + ticks: { + source: source + } + } + } + } + }); + }); + + it ('should not add offset from the edges', function() { + var scale = this.chart.scales.x; + + expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left); + expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width); + }); + + it ('should add offset from the edges if offset is true', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.offset = true; + chart.update(); + + var numTicks = scale.ticks.length; + var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); + var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2); + + expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2); + expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2); + }); + + it ('should not add offset if min and max extend the labels range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2051'; + chart.update(); + + expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left); + expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width); + }); + }); + }); + }); + + it ('should handle offset when there are more data points than ticks', function() { + const chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [{x: 631180800000, y: '31.84'}, {x: 631267200000, y: '30.89'}, {x: 631353600000, y: '33.00'}, {x: 631440000000, y: '33.52'}, {x: 631526400000, y: '32.24'}, {x: 631785600000, y: '32.74'}, {x: 631872000000, y: '31.45'}, {x: 631958400000, y: '32.60'}, {x: 632044800000, y: '31.77'}, {x: 632131200000, y: '32.45'}, {x: 632390400000, y: '31.13'}, {x: 632476800000, y: '31.82'}, {x: 632563200000, y: '30.81'}, {x: 632649600000, y: '30.07'}, {x: 632736000000, y: '29.31'}, {x: 632995200000, y: '29.82'}, {x: 633081600000, y: '30.20'}, {x: 633168000000, y: '30.78'}, {x: 633254400000, y: '30.72'}, {x: 633340800000, y: '31.62'}, {x: 633600000000, y: '30.64'}, {x: 633686400000, y: '32.36'}, {x: 633772800000, y: '34.66'}, {x: 633859200000, y: '33.96'}, {x: 633945600000, y: '34.20'}, {x: 634204800000, y: '32.20'}, {x: 634291200000, y: '32.44'}, {x: 634377600000, y: '32.72'}, {x: 634464000000, y: '32.95'}, {x: 634550400000, y: '32.95'}, {x: 634809600000, y: '30.88'}, {x: 634896000000, y: '29.44'}, {x: 634982400000, y: '29.36'}, {x: 635068800000, y: '28.84'}, {x: 635155200000, y: '30.85'}, {x: 635414400000, y: '32.00'}, {x: 635500800000, y: '32.74'}, {x: 635587200000, y: '33.16'}, {x: 635673600000, y: '34.73'}, {x: 635760000000, y: '32.89'}, {x: 636019200000, y: '32.41'}, {x: 636105600000, y: '31.15'}, {x: 636192000000, y: '30.63'}, {x: 636278400000, y: '29.60'}, {x: 636364800000, y: '29.31'}, {x: 636624000000, y: '29.83'}, {x: 636710400000, y: '27.97'}, {x: 636796800000, y: '26.18'}, {x: 636883200000, y: '26.06'}, {x: 636969600000, y: '26.34'}, {x: 637228800000, y: '27.75'}, {x: 637315200000, y: '29.05'}, {x: 637401600000, y: '28.82'}, {x: 637488000000, y: '29.43'}, {x: 637574400000, y: '29.53'}, {x: 637833600000, y: '28.50'}, {x: 637920000000, y: '28.87'}, {x: 638006400000, y: '28.11'}, {x: 638092800000, y: '27.79'}, {x: 638179200000, y: '28.18'}, {x: 638438400000, y: '28.27'}, {x: 638524800000, y: '28.29'}, {x: 638611200000, y: '29.63'}, {x: 638697600000, y: '29.13'}, {x: 638784000000, y: '26.57'}, {x: 639039600000, y: '27.19'}, {x: 639126000000, y: '27.48'}, {x: 639212400000, y: '27.79'}, {x: 639298800000, y: '28.48'}, {x: 639385200000, y: '27.88'}, {x: 639644400000, y: '25.63'}, {x: 639730800000, y: '25.02'}, {x: 639817200000, y: '25.26'}, {x: 639903600000, y: '25.00'}, {x: 639990000000, y: '26.23'}, {x: 640249200000, y: '26.22'}, {x: 640335600000, y: '26.36'}, {x: 640422000000, y: '25.45'}, {x: 640508400000, y: '24.62'}, {x: 640594800000, y: '26.65'}, {x: 640854000000, y: '26.28'}, {x: 640940400000, y: '27.25'}, {x: 641026800000, y: '25.93'}], + backgroundColor: '#ff6666' + }] + }, + options: { + scales: { + x: { + type: 'timeseries', + offset: true, + ticks: { + source: 'data', + autoSkip: true, + maxRotation: 0 + } + }, + y: { + type: 'linear', + gridLines: { + drawBorder: false + } + } + } + }, + plugins: { + legend: false + } + }); + const scale = chart.scales.x; + expect(scale.getPixelForDecimal(0)).toBeCloseToPixel(29); + expect(scale.getPixelForDecimal(1.0)).toBeCloseToPixel(494); + }); + + ['data', 'labels'].forEach(function(source) { + ['timeseries', 'time'].forEach(function(type) { + describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + x: { + id: 'x', + type: type, + time: { + parser: 'YYYY' + }, + ticks: { + source: source + } + } + } + } + }); + }); + + it ('should add offset if min and max extend the labels range and offset is true', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.x; + + options.min = '2012'; + options.max = '2051'; + options.offset = true; + chart.update(); + + var numTicks = scale.ticks.length; + var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); + var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2); + expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2); + expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2); + }); + }); + }); + }); + + describe('Deprecations', function() { + describe('options.time.displayFormats', function() { + it('should generate defaults from adapter presets', function() { + var chart = window.acquireChart({ + type: 'line', + data: {}, + options: { + scales: { + x: { + type: 'time' + } + } + } + }); + + // NOTE: built-in adapter uses moment + var expected = { + datetime: 'MMM D, YYYY, h:mm:ss a', + millisecond: 'h:mm:ss.SSS a', + second: 'h:mm:ss a', + minute: 'h:mm a', + hour: 'hA', + day: 'MMM D', + week: 'll', + month: 'MMM YYYY', + quarter: '[Q]Q - YYYY', + year: 'YYYY' + }; + + expect(chart.scales.x.options.time.displayFormats).toEqual(expected); + expect(chart.options.scales.x.time.displayFormats).toEqual(expected); + }); + + it('should merge user formats with adapter presets', function() { + var chart = window.acquireChart({ + type: 'line', + data: {}, + options: { + scales: { + x: { + type: 'time', + time: { + displayFormats: { + millisecond: 'foo', + hour: 'bar', + month: 'bla' + } + } + } + } + } + }); + + // NOTE: built-in adapter uses moment + var expected = { + datetime: 'MMM D, YYYY, h:mm:ss a', + millisecond: 'foo', + second: 'h:mm:ss a', + minute: 'h:mm a', + hour: 'bar', + day: 'MMM D', + week: 'll', + month: 'bla', + quarter: '[Q]Q - YYYY', + year: 'YYYY' + }; + + expect(chart.scales.x.options.time.displayFormats).toEqual(expected); + expect(chart.options.scales.x.time.displayFormats).toEqual(expected); + }); + }); + }); }); diff --git a/types/animation.d.ts b/types/animation.d.ts index 9f78e06c9c1..8eff8baac43 100644 --- a/types/animation.d.ts +++ b/types/animation.d.ts @@ -2,11 +2,11 @@ import { Chart } from './index.esm'; import { AnyObject } from './basic'; export class Animation { - constructor(cfg: AnyObject, target: AnyObject, prop: string, to?: unknown); - active(): boolean; - update(cfg: AnyObject, to: unknown, date: number): void; - cancel(): void; - tick(date: number): void; + constructor(cfg: AnyObject, target: AnyObject, prop: string, to?: unknown); + active(): boolean; + update(cfg: AnyObject, to: unknown, date: number): void; + cancel(): void; + tick(date: number): void; } export interface AnimationEvent { @@ -16,17 +16,17 @@ export interface AnimationEvent { } export class Animator { - listen(chart: Chart, event: 'complete' | 'progress', cb: (event: AnimationEvent) => void): void; - add(chart: Chart, items: readonly Animation[]): void; - has(chart: Chart): boolean; - start(chart: Chart): void; - running(chart: Chart): boolean; - stop(chart: Chart): void; - remove(chart: Chart): boolean; + listen(chart: Chart, event: 'complete' | 'progress', cb: (event: AnimationEvent) => void): void; + add(chart: Chart, items: readonly Animation[]): void; + has(chart: Chart): boolean; + start(chart: Chart): void; + running(chart: Chart): boolean; + stop(chart: Chart): void; + remove(chart: Chart): boolean; } export class Animations { - constructor(chart: Chart, animations: AnyObject); - configure(animations: AnyObject): void; - update(target: AnyObject, values: AnyObject): undefined | boolean; + constructor(chart: Chart, animations: AnyObject); + configure(animations: AnyObject): void; + update(target: AnyObject, values: AnyObject): undefined | boolean; } diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index b6827e7591f..799be04f838 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -20,9 +20,9 @@ import { Element } from './element'; import { ChartArea, Point } from './geometric'; import { LayoutItem, LayoutPosition } from './layout'; import { - Scriptable, - ScriptableOptions, - ScriptableAndArrayOptions + Scriptable, + ScriptableOptions, + ScriptableAndArrayOptions } from './scriptable'; export { DateAdapter, TimeUnit, _adapters } from './adapters'; @@ -32,10 +32,10 @@ export { Element } from './element'; export { ChartArea, Point } from './geometric'; export { LayoutItem, LayoutPosition } from './layout'; export { - Scriptable, - ScriptableOptions, - ScriptableAndArray, - ScriptableAndArrayOptions + Scriptable, + ScriptableOptions, + ScriptableAndArray, + ScriptableAndArrayOptions } from './scriptable'; export interface ScriptableContext { @@ -519,7 +519,7 @@ export declare enum UpdateModeEnum { export type UpdateMode = keyof typeof UpdateModeEnum; export class DatasetController { - constructor(chart: Chart, datasetIndex: number); + constructor(chart: Chart, datasetIndex: number); readonly chart: Chart; readonly index: number; @@ -1863,13 +1863,13 @@ export interface ElementChartOptions { } export class BasePlatform { - /** + /** * Called at chart construction time, returns a context2d instance implementing * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. * @param {HTMLCanvasElement} canvas - The canvas from which to acquire context (platform specific) * @param options - The chart options */ - acquireContext( + acquireContext( canvas: HTMLCanvasElement, options?: CanvasRenderingContext2DSettings ): CanvasRenderingContext2D | null; @@ -1879,39 +1879,39 @@ export class BasePlatform { * @param {CanvasRenderingContext2D} context - The context2d instance * @returns {boolean} true if the method succeeded, else false */ - releaseContext(context: CanvasRenderingContext2D): boolean; - /** + releaseContext(context: CanvasRenderingContext2D): boolean; + /** * Registers the specified listener on the given chart. * @param {Chart} chart - Chart from which to listen for event * @param {string} type - The ({@link ChartEvent}) type to listen for * @param listener - Receives a notification (an object that implements * the {@link ChartEvent} interface) when an event of the specified type occurs. */ - addEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void; - /** + addEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void; + /** * Removes the specified listener previously registered with addEventListener. * @param {Chart} chart - Chart from which to remove the listener * @param {string} type - The ({@link ChartEvent}) type to remove * @param listener - The listener function to remove from the event target. */ - removeEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void; - /** + removeEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void; + /** * @returns {number} the current devicePixelRatio of the device this platform is connected to. */ - getDevicePixelRatio(): number; - /** + getDevicePixelRatio(): number; + /** * @param {HTMLCanvasElement} canvas - The canvas for which to calculate the maximum size * @param {number} [width] - Parent element's content width * @param {number} [height] - Parent element's content height * @param {number} [aspectRatio] - The aspect ratio to maintain * @returns { width: number, height: number } the maximum size available. */ - getMaximumSize(canvas: HTMLCanvasElement, width?: number, height?: number, aspectRatio?: number): { width: number, height: number }; - /** + getMaximumSize(canvas: HTMLCanvasElement, width?: number, height?: number, aspectRatio?: number): { width: number, height: number }; + /** * @param {HTMLCanvasElement} canvas * @returns {boolean} true if the canvas is attached to the platform, false if not. */ - isAttached(canvas: HTMLCanvasElement): boolean; + isAttached(canvas: HTMLCanvasElement): boolean; } export class BasicPlatform extends BasePlatform {}