From 374b7491a32c4c5d7220ef906e65abcdd47bba42 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Mon, 16 Dec 2019 18:17:42 -0500 Subject: [PATCH] Allow axes to be centered on the chart area (#6818) Allow axes to be centered on the chart area or at a dynamic position based on another axis --- docs/axes/cartesian/README.md | 19 ++- samples/samples.js | 3 + samples/scales/axis-center-position.html | 117 ++++++++++++++++++ src/core/core.controller.js | 7 +- src/core/core.layouts.js | 32 +++-- src/core/core.scale.js | 97 +++++++++++---- src/scales/scale.category.js | 1 - src/scales/scale.linear.js | 1 - src/scales/scale.logarithmic.js | 2 - src/scales/scale.time.js | 2 - .../core.scale/x-axis-position-center.json | 57 +++++++++ .../core.scale/x-axis-position-center.png | Bin 0 -> 4187 bytes .../core.scale/x-axis-position-dynamic.json | 59 +++++++++ .../core.scale/x-axis-position-dynamic.png | Bin 0 -> 4103 bytes .../core.scale/y-axis-position-center.json | 57 +++++++++ .../core.scale/y-axis-position-center.png | Bin 0 -> 4964 bytes .../core.scale/y-axis-position-dynamic.json | 59 +++++++++ .../core.scale/y-axis-position-dynamic.png | Bin 0 -> 5093 bytes test/specs/scale.category.tests.js | 3 +- test/specs/scale.linear.tests.js | 1 - test/specs/scale.logarithmic.tests.js | 1 - test/specs/scale.time.tests.js | 1 - 22 files changed, 466 insertions(+), 53 deletions(-) create mode 100644 samples/scales/axis-center-position.html create mode 100644 test/fixtures/core.scale/x-axis-position-center.json create mode 100644 test/fixtures/core.scale/x-axis-position-center.png create mode 100644 test/fixtures/core.scale/x-axis-position-dynamic.json create mode 100644 test/fixtures/core.scale/x-axis-position-dynamic.png create mode 100644 test/fixtures/core.scale/y-axis-position-center.json create mode 100644 test/fixtures/core.scale/y-axis-position-center.png create mode 100644 test/fixtures/core.scale/y-axis-position-dynamic.json create mode 100644 test/fixtures/core.scale/y-axis-position-dynamic.png diff --git a/docs/axes/cartesian/README.md b/docs/axes/cartesian/README.md index 6b9b98253..91bde5954 100644 --- a/docs/axes/cartesian/README.md +++ b/docs/axes/cartesian/README.md @@ -14,13 +14,30 @@ All of the included cartesian axes support a number of common options. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- | `type` | `string` | | Type of scale being employed. Custom scales can be created and registered with a string key. This allows changing the type of an axis for a chart. -| `position` | `string` | | Position of the axis in the chart. Possible values are: `'top'`, `'left'`, `'bottom'`, `'right'` +| `position` | `string` | | Position of the axis. [more...](#axis-position) +| `axis` | `string` | | Which type of axis this is. Possible values are: `'x'`, `'y'`. If not set, this is inferred from the first character of the ID which should be `'x'` or `'y'`. | `offset` | `boolean` | `false` | If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to `true` for a bar chart by default. | `id` | `string` | | The ID is used to link datasets and scale axes together. [more...](#axis-id) | `gridLines` | `object` | | Grid line configuration. [more...](../styling.md#grid-line-configuration) | `scaleLabel` | `object` | | Scale title configuration. [more...](../labelling.md#scale-title-configuration) | `ticks` | `object` | | Tick configuration. [more...](#tick-configuration) +### Axis Position + +An axis can either be positioned at the edge of the chart, at the center of the chart area, or dynamically with respect to a data value. + +To position the axis at the edge of the chart, set the `position` option to one of: `'top'`, `'left'`, `'bottom'`, `'right'`. +To position the axis at the center of the chart area, set the `position` option to `'center'`. In this mode, either the `axis` option is specified or the axis ID starts with the letter 'x' or 'y'. +To position the axis with respect to a data value, set the `position` option to an object such as: + +```javascript +{ + x: -20 +} +``` + +This will position the axis at a value of -20 on the axis with ID "x". For cartesian axes, only 1 axis may be specified. + ### Tick Configuration The following options are common to all cartesian axes but do not apply to other axes. diff --git a/samples/samples.js b/samples/samples.js index 15ece3ff5..d1cae5672 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -148,6 +148,9 @@ }, { title: 'Axes Labels', path: 'scales/axes-labels.html' + }, { + title: 'Center Positioning', + path: 'scales/axis-center-position.html' }] }, { title: 'Legend', diff --git a/samples/scales/axis-center-position.html b/samples/scales/axis-center-position.html new file mode 100644 index 000000000..e3f942ca4 --- /dev/null +++ b/samples/scales/axis-center-position.html @@ -0,0 +1,117 @@ + + + + + Scatter Chart + + + + + + +
+ + + + diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 3ea05a925..320049212 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -147,8 +147,9 @@ function updateConfig(chart) { chart.tooltip.initialize(); } -function positionIsHorizontal(position) { - return position === 'top' || position === 'bottom'; +const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea']; +function positionIsHorizontal(position, axis) { + return position === 'top' || position === 'bottom' || (!KNOWN_POSITIONS.includes(position) && axis === 'x'); } function compare2Level(l1, l2) { @@ -341,7 +342,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { var id = scaleOptions.id; var scaleType = valueOrDefault(scaleOptions.type, item.dtype); - if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, scaleOptions.axis || id[0]) !== positionIsHorizontal(item.dposition)) { scaleOptions.position = item.dposition; } diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index a166649bd..ac1b3eec9 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -5,10 +5,14 @@ var helpers = require('../helpers/index'); var extend = helpers.extend; +const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom']; + function filterByPosition(array, position) { - return helpers.where(array, function(v) { - return v.pos === position; - }); + return helpers.where(array, v => v.pos === position); +} + +function filterDynamicPositionByAxis(array, axis) { + return helpers.where(array, v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis); } function sortByWeight(array, reverse) { @@ -52,18 +56,20 @@ function setLayoutDims(layouts, params) { } function buildLayoutBoxes(boxes) { - var layoutBoxes = wrapBoxes(boxes); - var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); - var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); - var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); - var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + 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(bottom), + rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), chartArea: filterByPosition(layoutBoxes, 'chartArea'), - vertical: left.concat(right), - horizontal: top.concat(bottom) + vertical: left.concat(right).concat(centerVertical), + horizontal: top.concat(bottom).concat(centerHorizontal) }; } @@ -375,7 +381,9 @@ module.exports = { left: chartArea.left, top: chartArea.top, right: chartArea.left + chartArea.w, - bottom: chartArea.top + chartArea.h + bottom: chartArea.top + chartArea.h, + height: chartArea.h, + width: chartArea.w, }; // Finally update boxes in chartArea (radial scale for example) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index dfef9f448..6adce4a89 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -14,7 +14,6 @@ const resolve = helpers.options.resolve; defaults._set('scale', { display: true, - position: 'left', offset: false, reverse: false, beginAtZero: false, @@ -669,7 +668,7 @@ class Scale extends Element { var scaleLabelOpts = opts.scaleLabel; var gridLineOpts = opts.gridLines; var display = me._isVisible(); - var isBottom = opts.position === 'bottom'; + var labelsBelowTicks = opts.position !== 'top' && me.axis === 'x'; var isHorizontal = me.isHorizontal(); // Width @@ -717,10 +716,10 @@ class Scale extends Element { // 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 = isBottom ? + paddingLeft = labelsBelowTicks ? cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : sinRotation * (firstLabelSize.height - firstLabelSize.offset); - paddingRight = isBottom ? + paddingRight = labelsBelowTicks ? sinRotation * (lastLabelSize.height - lastLabelSize.offset) : cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; } else { @@ -778,8 +777,8 @@ class Scale extends Element { // Shared Methods isHorizontal() { - var pos = this.options.position; - return pos === 'top' || pos === 'bottom'; + const {axis, position} = this.options; + return position === 'top' || position === 'bottom' || axis === 'x'; } isFullWidth() { return this.options.fullWidth; @@ -965,10 +964,10 @@ class Scale extends Element { */ _computeGridLineItems(chartArea) { var me = this; + const axis = me.axis; var chart = me.chart; var options = me.options; - var gridLines = options.gridLines; - var position = options.position; + const {gridLines, position} = options; var offsetGridLines = gridLines.offsetGridLines; var isHorizontal = me.isHorizontal(); var ticks = me.ticks; @@ -1006,12 +1005,38 @@ class Scale extends Element { tx2 = borderValue - axisHalfWidth; x1 = alignBorderValue(chartArea.left) + axisHalfWidth; x2 = chartArea.right; - } else { + } 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 (helpers.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 (helpers.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) { @@ -1067,20 +1092,20 @@ class Scale extends Element { /** * @private */ - _computeLabelItems() { - var me = this; - var options = me.options; - var optionTicks = options.ticks; - var position = options.position; - var isMirrored = optionTicks.mirror; - var isHorizontal = me.isHorizontal(); - var ticks = me.ticks; - var fonts = parseTickFontOptions(optionTicks); - var tickPadding = optionTicks.padding; - var tl = getTickMarkLength(options.gridLines); - var rotation = -helpers.toRadians(me.labelRotation); - var items = []; - var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + _computeLabelItems(chartArea) { + const me = this; + const axis = me.axis; + const options = me.options; + const {position, ticks: optionTicks} = options; + const isMirrored = optionTicks.mirror; + const isHorizontal = me.isHorizontal(); + const ticks = me.ticks; + const fonts = parseTickFontOptions(optionTicks); + const tickPadding = optionTicks.padding; + const tl = getTickMarkLength(options.gridLines); + const rotation = -helpers.toRadians(me.labelRotation); + const items = []; + let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; if (position === 'top') { y = me.bottom - tl - tickPadding; @@ -1091,9 +1116,27 @@ class Scale extends Element { } else if (position === 'left') { x = me.right - (isMirrored ? 0 : tl) - tickPadding; textAlign = isMirrored ? 'left' : 'right'; - } else { + } else if (position === 'right') { x = me.left + (isMirrored ? 0 : tl) + tickPadding; textAlign = isMirrored ? 'right' : 'left'; + } else if (axis === 'x') { + if (position === 'center') { + y = ((chartArea.top + chartArea.bottom) / 2) + tl + tickPadding; + } else if (helpers.isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + y = me.chart.scales[positionAxisID].getPixelForValue(value) + tl + tickPadding; + } + textAlign = !rotation ? 'center' : 'right'; + } else if (axis === 'y') { + if (position === 'center') { + x = ((chartArea.left + chartArea.right) / 2) - tl - tickPadding; + } else if (helpers.isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + x = me.chart.scales[positionAxisID].getPixelForValue(value); + } + textAlign = 'right'; } for (i = 0, ilen = ticks.length; i < ilen; ++i) { @@ -1214,7 +1257,7 @@ class Scale extends Element { /** * @private */ - _drawLabels() { + _drawLabels(chartArea) { var me = this; var optionTicks = me.options.ticks; @@ -1223,7 +1266,7 @@ class Scale extends Element { } var ctx = me.ctx; - var items = me._labelItems || (me._labelItems = me._computeLabelItems()); + var items = me._labelItems || (me._labelItems = me._computeLabelItems(chartArea)); var i, j, ilen, jlen, item, tickFont, label, y; for (i = 0, ilen = items.length; i < ilen; ++i) { @@ -1335,7 +1378,7 @@ class Scale extends Element { me._drawGrid(chartArea); me._drawTitle(); - me._drawLabels(); + me._drawLabels(chartArea); } /** diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index 147a42d3e..15d1afc55 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -3,7 +3,6 @@ import Scale from '../core/core.scale'; const defaultConfig = { - position: 'bottom' }; class CategoryScale extends Scale { diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 2670eb513..a26db1332 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -5,7 +5,6 @@ import LinearScaleBase from './scale.linearbase'; import Ticks from '../core/core.ticks'; const defaultConfig = { - position: 'left', ticks: { callback: Ticks.formatters.linear } diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index a5ae779b8..d48ce5b64 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -56,8 +56,6 @@ function generateTicks(generationOptions, dataRange) { } const defaultConfig = { - position: 'left', - // label settings ticks: { callback: Ticks.formatters.logarithmic diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 4741bcfcb..9e39e6a08 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -485,8 +485,6 @@ function filterBetween(timestamps, min, max) { } const defaultConfig = { - position: 'bottom', - /** * Data distribution along the scale: * - 'linear': data are spread according to their time (distances can vary), diff --git a/test/fixtures/core.scale/x-axis-position-center.json b/test/fixtures/core.scale/x-axis-position-center.json new file mode 100644 index 000000000..94583f9e3 --- /dev/null +++ b/test/fixtures/core.scale/x-axis-position-center.json @@ -0,0 +1,57 @@ +{ + "config": { + "type": "scatter", + "data": { + "datasets": [{ + "data": [{ + "x": -20, + "y": -30 + }, { + "x": 0, + "y": 0 + }, { + "x": 20, + "y": 15 + }] + }] + }, + "options": { + "legend": false, + "title": false, + "scales": { + "x": { + "position": "center", + "axis": "x", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + }, + "y": { + "position": "left", + "axis": "y", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/core.scale/x-axis-position-center.png b/test/fixtures/core.scale/x-axis-position-center.png new file mode 100644 index 0000000000000000000000000000000000000000..c9ef8c8785a1c14e73d41a2a3d485754fc6c6bc8 GIT binary patch literal 4187 zc-rk(X;@QN8ou|ENP<|_idBRNiGnPlZY)K(EDl5{P$UD1Y$}R^l$3}dWl0o4t1M*{ zkTq7N<0ULlg#iR1I;~I4^=5fkdmJ{0~G-C7BFtn zM|D@QXfTSx>m*Ue_|n`xD4fe+4byO(5~3o3vfZcvC6Jf`!<1#CjN{g+dqJxz{r9e* zNJlihpd+j&))X8k5Fi$UL2o<|iwnST%W8MPajQt$0@yT1bH%xk0}M{qiT4N`O$4b1 z*ImLxeP|$p9%fzVtK&ErCI>^UH;7Qz0wbGVc;+`q$pK#O;9WN+PIb=6p zx=awZCU%4<>JXxW&IrUrw3@(h>~JR-;g7;c^)l!17NL$P?E2?C7LLkWZ3ML`eWUlA zCL@W$Y$7%`_(Yhi?hn14j>FZvgZ{}{xl)wN|88=>L#Kp;KG8qke&JnRXK}o6)CM6= zL6Wd7awPl{FX**e%SP(|ZE^nNWU}{l92=M32;(2njm7p`ZDY z=D$fUh5Ss6Rrqw|l#ac?7Ei>UX3}`dKzKGLPn;u7xk_2=gz4oH#XQDU$RDxi@;}1TSsI`;)5^pF#U!>ker-uy zj5LTb`;_TOTcH!x2ul5;^&1jpSzl<0jVTXXF8MN-xNP~8%p2|(4@#JeI7qG=h}(S4zSq^CsFUdfr6hQHECN_fBx_fN@Lw zr0JtzzjYHiWnO{Z))G*}48)xPZZaR^^-=3xTJn72p17|&%E1Q#2M|6k z10N<1>z#1grPD=_uCZA?!8;S+!}s)#6kGyyStxAf%{@t6P<(-pa?&wXoZ(hk0)%(H zYJ#A6Iq>06eX@=SiVlKuJ=Sv>_uWRW5Ip@;|IR)B! zPOBcq102~Rf@WL4{R5$mKq$XG-z)VXb#^@&Qrzq;%(Vg%ng-f=zu#|C9SOp)NS}_U zeE3Zz)|s>nLO{boG|{%%;pUZ%iHUw}HJ};nI~;bTZ^v}$m|9y*4N&SB;P>YRCFvU- zYJ4@;$Y$&B&XcWBeMgUu7TDHQ1SJd8in-GK;f%NT2`3)R{RB7dPTFd*Q}9;}8qXgT zs zx87e218IhU48#?qGpofFS7X}kmunJDf)ic@wF-h(#)Fv%s!DH;B1&K=ftaYb7du5sZV+b`pK8TI&UIn-5tNvkpkww(=5!GC)Mp z@mN!0)@?&tZ;n+(&%PVvzg9B%>`r&<7`j&(T{2D~j=G$@tLbXR9tXJFo)$xYcgy*u x$>9Hy&gA?HX$rb<8mUY|r(EA%)_-X-FPTftvW@X-9ZU#(SbIF2%N>JH{~du#F)#oC literal 0 Hc-jL100001 diff --git a/test/fixtures/core.scale/x-axis-position-dynamic.json b/test/fixtures/core.scale/x-axis-position-dynamic.json new file mode 100644 index 000000000..84e80670c --- /dev/null +++ b/test/fixtures/core.scale/x-axis-position-dynamic.json @@ -0,0 +1,59 @@ +{ + "config": { + "type": "scatter", + "data": { + "datasets": [{ + "data": [{ + "x": -20, + "y": -30 + }, { + "x": 0, + "y": 0 + }, { + "x": 20, + "y": 15 + }] + }] + }, + "options": { + "legend": false, + "title": false, + "scales": { + "x": { + "position": { + "y": 30 + }, + "axis": "x", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + }, + "y": { + "position": "left", + "axis": "y", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/core.scale/x-axis-position-dynamic.png b/test/fixtures/core.scale/x-axis-position-dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..0ac0a903c3211247ed46fee0f4baf105a58b9b8c GIT binary patch literal 4103 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 Vm$siq*<%F$++Dn#OC0%e{{ul0cewxn literal 0 Hc-jL100001 diff --git a/test/fixtures/core.scale/y-axis-position-dynamic.json b/test/fixtures/core.scale/y-axis-position-dynamic.json new file mode 100644 index 000000000..c0f9bc304 --- /dev/null +++ b/test/fixtures/core.scale/y-axis-position-dynamic.json @@ -0,0 +1,59 @@ +{ + "config": { + "type": "scatter", + "data": { + "datasets": [{ + "data": [{ + "x": -20, + "y": -30 + }, { + "x": 0, + "y": 0 + }, { + "x": 20, + "y": 15 + }] + }] + }, + "options": { + "legend": false, + "title": false, + "scales": { + "x": { + "position": "bottom", + "axis": "x", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + }, + "y": { + "position": { + "x": -50 + }, + "axis": "y", + "min": -100, + "max": 100, + "gridLines": { + "color": "red", + "drawOnChartArea": false + }, + "ticks": { + "display": false + } + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/core.scale/y-axis-position-dynamic.png b/test/fixtures/core.scale/y-axis-position-dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..70f4efbf30981ae17a507d62252f9eca7a54c710 GIT binary patch literal 5093 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%0D+VU-T;u*aH*anIWHoD z#tp@XsI^56G$tV;W1}LuXzlbv@Xksoy4*J-i=ENeZJV{I1quvXvvt~6Vzz1tPr>`9 zwFK4^yJ6FI8qdPsM;i=NYf$y}ONxr5(?3+GJ9Ir5_DT6o*$KwGk3T>>L79u_O3~gJ zTb(DQ7AUyQFO{x%96%T7+AWiWl>Tci@%X%*#=T)Xle$$O1*MQ+e>DuE;WtGqD3(o{B{+= zE9pMnfew5?uCzu6Yd9sE@np8IVa27b$qQ9))NcE0wv5!M-$rK$)q%(MH`wSG!@5TX zE;oG}c7Bzg8s>maCnJ4i_AhX(YaAgn%FjoGOH9iTdN0av zy~!_gGNcpYPdmd8rhYzs?ab{dZ@t%{o6MXxfltU)j*2+f|Wl5cvKsj{i{L*&%j$Y=!ki zxE2-Mezsy)n%~_X-kuT=P`tbG%=_0Hdq#$$5r^9~bNOvSK7t=Rt2LHI7Tg2>1cMo) A7XSbN literal 0 Hc-jL100001 diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 5485f137e..daf69e7ff 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -34,7 +34,6 @@ describe('Category scale tests', function() { borderDash: [], borderDashOffset: 0.0 }, - position: 'bottom', offset: false, scaleLabel: Chart.defaults.scale.scaleLabel, ticks: { @@ -68,6 +67,7 @@ describe('Category scale tests', function() { }; var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category')); + config.position = 'bottom'; var Constructor = Chart.scaleService.getScaleConstructor('category'); var scale = new Constructor({ ctx: {}, @@ -95,6 +95,7 @@ describe('Category scale tests', function() { }; var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category')); + config.position = 'bottom'; var Constructor = Chart.scaleService.getScaleConstructor('category'); var scale = new Constructor({ ctx: {}, diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 459db671c..90a4f05fd 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -25,7 +25,6 @@ describe('Linear Scale', function() { borderDash: [], borderDashOffset: 0.0 }, - position: 'left', offset: false, reverse: false, beginAtZero: false, diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 599dfd38e..5c0fa9cdc 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -25,7 +25,6 @@ describe('Logarithmic Scale tests', function() { borderDash: [], borderDashOffset: 0.0 }, - position: 'left', offset: false, reverse: false, beginAtZero: false, diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 09fc2ff02..465a7e0f4 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -72,7 +72,6 @@ describe('Time scale tests', function() { borderDash: [], borderDashOffset: 0.0 }, - position: 'bottom', offset: false, reverse: false, beginAtZero: false, -- 2.47.2