Scale: draw offset grid for labels before autoSkip (#8748)

* Scale: draw offset grid for labels before autoSkip
* fix tests
This commit is contained in:
Jukka Kurkela
2021-03-29 23:53:47 +03:00
committed by GitHub
parent fe406bf717
commit cdba66ccef
21 changed files with 99 additions and 33 deletions

View File

@@ -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.

View File

@@ -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 = [];

View File

@@ -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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View 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
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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() {

View File

@@ -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) {