Scale: draw offset grid for labels before autoSkip (#8748)
* Scale: draw offset grid for labels before autoSkip * fix tests
@@ -23,7 +23,7 @@ Namespace: `options.scales[scaleId].grid`, it defines options for the grid lines
|
||||
| `drawOnChartArea` | `boolean` | | | `true` | If true, draw lines on the chart area inside the axis lines. This is useful when there are multiple axes and you need to control which grid lines are drawn.
|
||||
| `drawTicks` | `boolean` | | | `true` | If true, draw lines beside the ticks in the axis area beside the chart.
|
||||
| `lineWidth` | `number` | Yes | Yes | `1` | Stroke width of grid lines.
|
||||
| `offset` | `boolean` | | | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` for a bar chart by default.
|
||||
| `offset` | `boolean` | | | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` for a bar chart by default. Note: AutoSkip does not remove offset grid lines.
|
||||
| `tickBorderDash` | `number[]` | | | | Length and spacing of the tick mark line. If not set, defaults to the grid line `borderDash` value.
|
||||
| `tickBorderDashOffset` | `number` | Yes | Yes | | Offset for the line dash of the tick mark. If unset, defaults to the grid line `borderDashOffset` value
|
||||
| `tickColor` | [`Color`](../general/colors.md) | Yes | Yes | | Color of the tick line. If unset, defaults to the grid line color.
|
||||
|
||||
@@ -42,21 +42,21 @@ function sample(arr, numItems) {
|
||||
* @param {boolean} offsetGridLines
|
||||
*/
|
||||
function getPixelForGridLine(scale, index, offsetGridLines) {
|
||||
const length = scale.ticks.length;
|
||||
const length = offsetGridLines ? scale._allTicks.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 lineValue = scale.getPixelForTick(validIndex, offsetGridLines);
|
||||
let offset;
|
||||
|
||||
if (offsetGridLines) {
|
||||
if (length === 1) {
|
||||
offset = Math.max(lineValue - start, end - lineValue);
|
||||
} else if (index === 0) {
|
||||
offset = (scale.getPixelForTick(1) - lineValue) / 2;
|
||||
offset = (scale.getPixelForTick(1, offsetGridLines) - lineValue) / 2;
|
||||
} else {
|
||||
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
|
||||
offset = (lineValue - scale.getPixelForTick(validIndex - 1, offsetGridLines)) / 2;
|
||||
}
|
||||
lineValue += validIndex < index ? offset : -offset;
|
||||
|
||||
@@ -205,7 +205,7 @@ export default class Scale extends Element {
|
||||
this.min = undefined;
|
||||
this.max = undefined;
|
||||
/** @type {Tick[]} */
|
||||
this.ticks = [];
|
||||
this.ticks = this._allTicks = [];
|
||||
/** @type {object[]|null} */
|
||||
this._gridLineItems = null;
|
||||
/** @type {object[]|null} */
|
||||
@@ -428,6 +428,7 @@ export default class Scale extends Element {
|
||||
me.afterCalculateLabelRotation();
|
||||
|
||||
// Auto-skip
|
||||
me._allTicks = me.ticks;
|
||||
if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
|
||||
me.ticks = autoSkip(me, me.ticks);
|
||||
me._labelSizes = null;
|
||||
@@ -666,7 +667,7 @@ export default class Scale extends Element {
|
||||
|
||||
_calculatePadding(first, last, sin, cos) {
|
||||
const me = this;
|
||||
const {ticks: {align, padding}, position} = me.options;
|
||||
const {position, ticks: {align, padding}} = me.options;
|
||||
const isRotated = me.labelRotation !== 0;
|
||||
const labelsBelowTicks = position !== 'top' && me.axis === 'x';
|
||||
|
||||
@@ -696,8 +697,8 @@ export default class Scale extends Element {
|
||||
}
|
||||
|
||||
// Adjust padding taking into account changes in offsets
|
||||
me.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * me.width / (me.width - offsetLeft), 0);
|
||||
me.paddingRight = Math.max((paddingRight - offsetRight + padding) * me.width / (me.width - offsetRight), 0);
|
||||
me.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * me.width / (me.width - offsetLeft), padding);
|
||||
me.paddingRight = Math.max((paddingRight - offsetRight + padding) * me.width / (me.width - offsetRight), padding);
|
||||
} else {
|
||||
let paddingTop = last.height / 2;
|
||||
let paddingBottom = first.height / 2;
|
||||
@@ -871,10 +872,11 @@ export default class Scale extends Element {
|
||||
* 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
|
||||
* @param {boolean} [all] - use ticks before autoSkip
|
||||
* @return {number}
|
||||
*/
|
||||
getPixelForTick(index) {
|
||||
const ticks = this.ticks;
|
||||
getPixelForTick(index, all = false) {
|
||||
const ticks = all ? this._allTicks : this.ticks;
|
||||
if (index < 0 || index > ticks.length - 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -992,7 +994,7 @@ export default class Scale extends Element {
|
||||
const {grid, position} = options;
|
||||
const offset = grid.offset;
|
||||
const isHorizontal = me.isHorizontal();
|
||||
const ticks = me.ticks;
|
||||
const ticks = offset ? me._allTicks : me.ticks;
|
||||
const ticksLength = ticks.length + (offset ? 1 : 0);
|
||||
const tl = getTickMarkLength(grid);
|
||||
const items = [];
|
||||
|
||||
@@ -109,17 +109,6 @@ export default class CategoryScale extends Scale {
|
||||
return value === null ? NaN : 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);
|
||||
|
||||
BIN
test/fixtures/core.scale/autoSkip/offset.png
vendored
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
38
test/fixtures/scale.category/autoskip-grid-x.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
module.exports = {
|
||||
config: {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Label 1', 'Label 2', 'Label 3', 'Label 4', 'Label 5', 'Label 6', 'Label 7', 'Label 8', 'Label 9', 'Label 10', 'Label 11', 'Label 12'],
|
||||
datasets: [{
|
||||
data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
}, {
|
||||
data: [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
||||
}, {
|
||||
data: [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
maxRotation: 0
|
||||
},
|
||||
grid: {
|
||||
color: 'red',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
spriteText: true,
|
||||
canvas: {
|
||||
width: 512,
|
||||
height: 256
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/scale.category/autoskip-grid-x.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
37
test/fixtures/scale.category/autoskip-grid-y.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
config: {
|
||||
type: 'bar',
|
||||
data: {
|
||||
// labels: [['Label 1', 'Line 2'], ['Label 2', 'Line 2'], ['Label 3', 'Line 2'], ['Label 4', 'Line 2'], ['Label 5', 'Line 2'], ['Label 6', 'Line 2'], ['Label 7', 'Line 2'], ['Label 8', 'Line 2'], ['Label 9', 'Line 2'], ['Label 10', 'Line 2'], ['Label 11', 'Line 2'], ['Label 12', 'Line 2']],
|
||||
labels: ['Label 1', 'Label 2', 'Label 3', 'Label 4', 'Label 5', 'Label 6', 'Label 7', 'Label 8', 'Label 9', 'Label 10', 'Label 11', 'Label 12'],
|
||||
datasets: [{
|
||||
data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
}, {
|
||||
data: [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
||||
}, {
|
||||
data: [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
color: 'red',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
x: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
spriteText: true,
|
||||
canvas: {
|
||||
width: 128,
|
||||
height: 200
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/scale.category/autoskip-grid-y.png
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
test/fixtures/scale.time/offset-with-1-tick.png
vendored
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
test/fixtures/scale.time/offset-with-2-ticks.png
vendored
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
test/fixtures/scale.timeseries/financial-daily.png
vendored
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
@@ -32,13 +32,13 @@ describe('Chart.layouts', function() {
|
||||
|
||||
expect(chart.chartArea.bottom).toBeCloseToPixel(120);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(31);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(250);
|
||||
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(31);
|
||||
expect(chart.scales.x.right).toBeCloseToPixel(250);
|
||||
expect(chart.scales.x.right).toBeCloseToPixel(247);
|
||||
expect(chart.scales.x.top).toBeCloseToPixel(120);
|
||||
expect(chart.scales.x.labelRotation).toBeCloseTo(0);
|
||||
|
||||
@@ -79,13 +79,13 @@ describe('Chart.layouts', function() {
|
||||
});
|
||||
|
||||
expect(chart.chartArea.bottom).toBeCloseToPixel(139);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(0);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(3);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(218);
|
||||
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(0);
|
||||
expect(chart.scales.x.left).toBeCloseToPixel(3);
|
||||
expect(chart.scales.x.right).toBeCloseToPixel(218);
|
||||
expect(chart.scales.x.top).toBeCloseToPixel(32);
|
||||
expect(chart.scales.x.labelRotation).toBeCloseTo(0);
|
||||
@@ -160,13 +160,13 @@ describe('Chart.layouts', function() {
|
||||
|
||||
expect(chart.chartArea.bottom).toBeCloseToPixel(110);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(70);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(250);
|
||||
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(70);
|
||||
expect(chart.scales.x.right).toBeCloseToPixel(250);
|
||||
expect(chart.scales.x.right).toBeCloseToPixel(247);
|
||||
expect(chart.scales.x.top).toBeCloseToPixel(110);
|
||||
expect(chart.scales.x.labelRotation).toBeCloseTo(40, -1);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Platform.basic', function() {
|
||||
|
||||
expect(chart.chartArea.bottom).toBeCloseToPixel(120);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(31);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(250);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(247);
|
||||
expect(chart.chartArea.top).toBeCloseToPixel(32);
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('Platform.basic', function() {
|
||||
|
||||
expect(chart.chartArea.bottom).toBeCloseToPixel(150);
|
||||
expect(chart.chartArea.left).toBeCloseToPixel(31);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(300);
|
||||
expect(chart.chartArea.right).toBeCloseToPixel(297);
|
||||
expect(chart.chartArea.top).toBeCloseToPixel(32);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -473,7 +473,7 @@ describe('Category scale tests', function() {
|
||||
var xScale = chart.scales.x;
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89);
|
||||
expect(xScale.getPixelForValue(3)).toBeCloseToPixel(451);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(572);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(570);
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for an object value in a horizontal bar chart', function() {
|
||||
|
||||
@@ -1114,7 +1114,7 @@ describe('Time scale tests', function() {
|
||||
});
|
||||
const scale = chart.scales.x;
|
||||
expect(scale.getPixelForDecimal(0)).toBeCloseToPixel(29);
|
||||
expect(scale.getPixelForDecimal(1.0)).toBeCloseToPixel(512);
|
||||
expect(scale.getPixelForDecimal(1.0)).toBeCloseToPixel(509);
|
||||
});
|
||||
|
||||
['data', 'labels'].forEach(function(source) {
|
||||
|
||||