Skip to content

Commit 416062b

Browse files
authored
Update tick positioning (#8657)
* Update tick positioning * Update tests
1 parent cc7a3fd commit 416062b

File tree

130 files changed

+271
-181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+271
-181
lines changed

src/core/core.scale.js

Lines changed: 99 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ defaults.set('scale', {
4343
drawBorder: true,
4444
drawOnChartArea: true,
4545
drawTicks: true,
46-
tickLength: 10,
46+
tickLength: 8,
4747
tickWidth: (_ctx, options) => options.lineWidth,
4848
tickColor: (_ctx, options) => options.color,
4949
offset: false,
@@ -75,7 +75,7 @@ defaults.set('scale', {
7575
mirror: false,
7676
textStrokeWidth: 0,
7777
textStrokeColor: '',
78-
padding: 0,
78+
padding: 3,
7979
display: true,
8080
autoSkip: true,
8181
autoSkipPadding: 3,
@@ -783,7 +783,7 @@ export default class Scale extends Element {
783783

784784
const labelSizes = me._getLabelSizes();
785785
const maxLabelWidth = labelSizes.widest.width;
786-
const maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
786+
const maxLabelHeight = labelSizes.highest.height;
787787

788788
// Estimate the width of each grid based on the canvas width, the maximum
789789
// label width and the number of tick intervals
@@ -822,104 +822,40 @@ export default class Scale extends Element {
822822
height: 0
823823
};
824824

825-
const chart = me.chart;
826-
const opts = me.options;
827-
const tickOpts = opts.ticks;
828-
const titleOpts = opts.title;
829-
const gridLineOpts = opts.grid;
825+
const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = me;
830826
const display = me._isVisible();
831-
const labelsBelowTicks = opts.position !== 'top' && me.axis === 'x';
832827
const isHorizontal = me.isHorizontal();
833-
const titleHeight = display && getTitleHeight(titleOpts, chart.options.font);
834-
835-
// Width
836-
if (isHorizontal) {
837-
minSize.width = me.maxWidth;
838-
} else if (display) {
839-
minSize.width = getTickMarkLength(gridLineOpts) + titleHeight;
840-
}
841828

842-
// height
843-
if (!isHorizontal) {
844-
minSize.height = me.maxHeight; // fill all the height
845-
} else if (display) {
846-
minSize.height = getTickMarkLength(gridLineOpts) + titleHeight;
847-
}
829+
if (display) {
830+
const titleHeight = getTitleHeight(titleOpts, chart.options.font);
831+
if (isHorizontal) {
832+
minSize.width = me.maxWidth;
833+
minSize.height = getTickMarkLength(gridOpts) + titleHeight;
834+
} else {
835+
minSize.height = me.maxHeight; // fill all the height
836+
minSize.width = getTickMarkLength(gridOpts) + titleHeight;
837+
}
848838

849-
// Don't bother fitting the ticks if we are not showing the labels
850-
if (tickOpts.display && display && me.ticks.length) {
851-
const labelSizes = me._getLabelSizes();
852-
const firstLabelSize = labelSizes.first;
853-
const lastLabelSize = labelSizes.last;
854-
const widestLabelSize = labelSizes.widest;
855-
const highestLabelSize = labelSizes.highest;
856-
const lineSpace = highestLabelSize.offset * 0.8;
857-
const tickPadding = tickOpts.padding;
839+
// Don't bother fitting the ticks if we are not showing the labels
840+
if (tickOpts.display && me.ticks.length) {
841+
const {first, last, widest, highest} = me._getLabelSizes();
842+
const tickPadding = tickOpts.padding * 2;
843+
const angleRadians = toRadians(me.labelRotation);
844+
const cos = Math.cos(angleRadians);
845+
const sin = Math.sin(angleRadians);
858846

859-
if (isHorizontal) {
847+
if (isHorizontal) {
860848
// A horizontal axis is more constrained by the height.
861-
const isRotated = me.labelRotation !== 0;
862-
const angleRadians = toRadians(me.labelRotation);
863-
const cosRotation = Math.cos(angleRadians);
864-
const sinRotation = Math.sin(angleRadians);
865-
866-
const labelHeight = sinRotation * widestLabelSize.width
867-
+ cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
868-
+ (isRotated ? 0 : lineSpace); // padding
869-
870-
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
871-
872-
const offsetLeft = me.getPixelForTick(0) - me.left;
873-
const offsetRight = me.right - me.getPixelForTick(me.ticks.length - 1);
874-
let paddingLeft, paddingRight;
875-
876-
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
877-
// which means that the right padding is dominated by the font height
878-
if (isRotated) {
879-
paddingLeft = labelsBelowTicks ?
880-
cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
881-
sinRotation * (firstLabelSize.height - firstLabelSize.offset);
882-
paddingRight = labelsBelowTicks ?
883-
sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
884-
cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
885-
} else if (tickOpts.align === 'start') {
886-
paddingLeft = 0;
887-
paddingRight = lastLabelSize.width;
888-
} else if (tickOpts.align === 'end') {
889-
paddingLeft = firstLabelSize.width;
890-
paddingRight = 0;
849+
const labelHeight = sin * widest.width + cos * highest.height;
850+
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
891851
} else {
892-
paddingLeft = firstLabelSize.width / 2;
893-
paddingRight = lastLabelSize.width / 2;
894-
}
895-
896-
// Adjust padding taking into account changes in offsets
897-
// and add 3 px to move away from canvas edges
898-
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
899-
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
900-
} else {
901852
// A vertical axis is more constrained by the width. Labels are the
902853
// dominant factor here, so get that length first and account for padding
903-
const labelWidth = tickOpts.mirror ? 0 :
904-
// use lineSpace for consistency with horizontal axis
905-
// tickPadding is not implemented for horizontal
906-
widestLabelSize.width + tickPadding + lineSpace;
907-
908-
minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
909-
910-
let paddingTop = lastLabelSize.height / 2;
911-
let paddingBottom = firstLabelSize.height / 2;
912-
913-
if (tickOpts.align === 'start') {
914-
paddingTop = 0;
915-
paddingBottom = firstLabelSize.height;
916-
} else if (tickOpts.align === 'end') {
917-
paddingTop = lastLabelSize.height;
918-
paddingBottom = 0;
919-
}
854+
const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
920855

921-
me.paddingTop = paddingTop;
922-
me.paddingBottom = paddingBottom;
856+
minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth + tickPadding);
857+
}
858+
me._calculatePadding(first, last, sin, cos);
923859
}
924860
}
925861

@@ -934,6 +870,57 @@ export default class Scale extends Element {
934870
}
935871
}
936872

873+
_calculatePadding(first, last, sin, cos) {
874+
const me = this;
875+
const {ticks: {align, padding}, position} = me.options;
876+
const isRotated = me.labelRotation !== 0;
877+
const labelsBelowTicks = position !== 'top' && me.axis === 'x';
878+
879+
if (me.isHorizontal()) {
880+
const offsetLeft = me.getPixelForTick(0) - me.left;
881+
const offsetRight = me.right - me.getPixelForTick(me.ticks.length - 1);
882+
let paddingLeft = 0;
883+
let paddingRight = 0;
884+
885+
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
886+
// which means that the right padding is dominated by the font height
887+
if (isRotated) {
888+
if (labelsBelowTicks) {
889+
paddingLeft = cos * first.width;
890+
paddingRight = sin * last.height;
891+
} else {
892+
paddingLeft = sin * first.height;
893+
paddingRight = cos * last.width;
894+
}
895+
} else if (align === 'start') {
896+
paddingRight = last.width;
897+
} else if (align === 'end') {
898+
paddingLeft = first.width;
899+
} else {
900+
paddingLeft = first.width / 2;
901+
paddingRight = last.width / 2;
902+
}
903+
904+
// Adjust padding taking into account changes in offsets
905+
me.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * me.width / (me.width - offsetLeft), 0);
906+
me.paddingRight = Math.max((paddingRight - offsetRight + padding) * me.width / (me.width - offsetRight), 0);
907+
} else {
908+
let paddingTop = last.height / 2;
909+
let paddingBottom = first.height / 2;
910+
911+
if (align === 'start') {
912+
paddingTop = 0;
913+
paddingBottom = first.height;
914+
} else if (align === 'end') {
915+
paddingTop = last.height;
916+
paddingBottom = 0;
917+
}
918+
919+
me.paddingTop = paddingTop + padding;
920+
me.paddingBottom = paddingBottom + padding;
921+
}
922+
}
923+
937924
/**
938925
* Handle margins and padding interactions
939926
* @private
@@ -990,7 +977,13 @@ export default class Scale extends Element {
990977
let labelSizes = me._labelSizes;
991978

992979
if (!labelSizes) {
993-
me._labelSizes = labelSizes = me._computeLabelSizes();
980+
const sampleSize = me.options.ticks.sampleSize;
981+
let ticks = me.ticks;
982+
if (sampleSize < ticks.length) {
983+
ticks = sample(ticks, sampleSize);
984+
}
985+
986+
me._labelSizes = labelSizes = me._computeLabelSizes(ticks, ticks.length);
994987
}
995988

996989
return labelSizes;
@@ -1002,26 +995,17 @@ export default class Scale extends Element {
1002995
* @return {{ first: object, last: object, widest: object, highest: object }}
1003996
* @private
1004997
*/
1005-
_computeLabelSizes() {
1006-
const me = this;
1007-
const ctx = me.ctx;
1008-
const caches = me._longestTextCache;
1009-
const sampleSize = me.options.ticks.sampleSize;
998+
_computeLabelSizes(ticks, length) {
999+
const {ctx, _longestTextCache: caches} = this;
10101000
const widths = [];
10111001
const heights = [];
1012-
const offsets = [];
10131002
let widestLabelSize = 0;
10141003
let highestLabelSize = 0;
1015-
let ticks = me.ticks;
1016-
if (sampleSize < ticks.length) {
1017-
ticks = sample(ticks, sampleSize);
1018-
}
1019-
const length = ticks.length;
10201004
let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
10211005

10221006
for (i = 0; i < length; ++i) {
10231007
label = ticks[i].label;
1024-
tickFont = me._resolveTickFontOptions(i);
1008+
tickFont = this._resolveTickFontOptions(i);
10251009
ctx.font = fontString = tickFont.string;
10261010
cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
10271011
lineHeight = tickFont.lineHeight;
@@ -1043,7 +1027,6 @@ export default class Scale extends Element {
10431027
}
10441028
widths.push(width);
10451029
heights.push(height);
1046-
offsets.push(lineHeight / 2);
10471030
widestLabelSize = Math.max(width, widestLabelSize);
10481031
highestLabelSize = Math.max(height, highestLabelSize);
10491032
}
@@ -1052,13 +1035,7 @@ export default class Scale extends Element {
10521035
const widest = widths.indexOf(widestLabelSize);
10531036
const highest = heights.indexOf(highestLabelSize);
10541037

1055-
function valueAt(idx) {
1056-
return {
1057-
width: widths[idx] || 0,
1058-
height: heights[idx] || 0,
1059-
offset: offsets[idx] || 0
1060-
};
1061-
}
1038+
const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});
10621039

10631040
return {
10641041
first: valueAt(0),
@@ -1299,7 +1276,7 @@ export default class Scale extends Element {
12991276
tx2 = me.left + tl;
13001277
} else if (axis === 'x') {
13011278
if (position === 'center') {
1302-
borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2);
1279+
borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
13031280
} else if (isObject(position)) {
13041281
const positionAxisID = Object.keys(position)[0];
13051282
const value = position[positionAxisID];
@@ -1458,24 +1435,20 @@ export default class Scale extends Element {
14581435
x = pixel;
14591436
if (position === 'top') {
14601437
if (crossAlign === 'near' || rotation !== 0) {
1461-
textOffset = (Math.sin(rotation) * halfCount + 0.5) * lineHeight;
1462-
textOffset -= (rotation === 0 ? (lineCount - 0.5) : Math.cos(rotation) * halfCount) * lineHeight;
1438+
textOffset = -lineCount * lineHeight + lineHeight / 2;
14631439
} else if (crossAlign === 'center') {
1464-
textOffset = -1 * (labelSizes.highest.height / 2);
1465-
textOffset -= halfCount * lineHeight;
1440+
textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
14661441
} else {
1467-
textOffset = (-1 * labelSizes.highest.height) + (0.5 * lineHeight);
1442+
textOffset = -labelSizes.highest.height + lineHeight / 2;
14681443
}
14691444
} else {
14701445
// eslint-disable-next-line no-lonely-if
14711446
if (crossAlign === 'near' || rotation !== 0) {
1472-
textOffset = Math.sin(rotation) * halfCount * lineHeight;
1473-
textOffset += (rotation === 0 ? 0.5 : Math.cos(rotation) * halfCount) * lineHeight;
1447+
textOffset = lineHeight / 2;
14741448
} else if (crossAlign === 'center') {
1475-
textOffset = labelSizes.highest.height / 2;
1476-
textOffset -= halfCount * lineHeight;
1449+
textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
14771450
} else {
1478-
textOffset = labelSizes.highest.height - ((lineCount - 0.5) * lineHeight);
1451+
textOffset = labelSizes.highest.height - lineCount * lineHeight;
14791452
}
14801453
}
14811454
} else {
@@ -1522,12 +1495,10 @@ export default class Scale extends Element {
15221495

15231496
_getYAxisLabelAlignment(tl) {
15241497
const me = this;
1525-
const {position, ticks} = me.options;
1526-
const {crossAlign, mirror, padding} = ticks;
1498+
const {position, ticks: {crossAlign, mirror, padding}} = me.options;
15271499
const labelSizes = me._getLabelSizes();
15281500
const tickAndPadding = tl + padding;
15291501
const widest = labelSizes.widest.width;
1530-
const lineSpace = labelSizes.highest.offset * 0.8;
15311502

15321503
let textAlign;
15331504
let x;
@@ -1546,7 +1517,7 @@ export default class Scale extends Element {
15461517
x -= (widest / 2);
15471518
} else {
15481519
textAlign = 'left';
1549-
x = me.left + lineSpace;
1520+
x = me.left;
15501521
}
15511522
}
15521523
} else if (position === 'right') {
@@ -1563,7 +1534,7 @@ export default class Scale extends Element {
15631534
x += widest / 2;
15641535
} else {
15651536
textAlign = 'right';
1566-
x = me.right - lineSpace;
1537+
x = me.right;
15671538
}
15681539
}
15691540
} else {
0 Bytes
13 Bytes
-46 Bytes
5.39 KB
339 Bytes
-145 Bytes
-850 Bytes
528 Bytes
-230 Bytes

0 commit comments

Comments
 (0)