diff --git a/docs/axes/cartesian/_common_ticks.md b/docs/axes/cartesian/_common_ticks.md index aeb3992a0e1..ccc98588dd2 100644 --- a/docs/axes/cartesian/_common_ticks.md +++ b/docs/axes/cartesian/_common_ticks.md @@ -4,7 +4,7 @@ Namespace: `options.scales[scaleId].ticks` | Name | Type | Default | Description | ---- | ---- | ------- | ----------- -| `align` | `string` | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, or `'end'`. +| `align` | `string` | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, `'end'`, or `'inner'`. `inner` alignment means align `start` for first tick and `end` for the last tick of horizontal axis | `crossAlign` | `string` | `'near'` | The tick alignment perpendicular to the axis. Can be `'near'`, `'center'`, or `'far'`. See [Tick Alignment](/axes/cartesian/#tick-alignment) | `sampleSize` | `number` | `ticks.length` | The number of ticks to examine when deciding how many labels will fit. Setting a smaller value will be faster, but may be less accurate when there is large variability in label length. | `autoSkip` | `boolean` | `true` | If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to `maxRotation` before skipping any. Turn `autoSkip` off to show all labels no matter what. diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 97529e591a9..1e79c1d97ef 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -705,7 +705,7 @@ export default class Scale extends Element { paddingRight = last.width; } else if (align === 'end') { paddingLeft = first.width; - } else { + } else if (align !== 'inner') { paddingLeft = first.width / 2; paddingRight = last.width / 2; } @@ -1206,9 +1206,21 @@ export default class Scale extends Element { const color = optsAtIndex.color; const strokeColor = optsAtIndex.textStrokeColor; const strokeWidth = optsAtIndex.textStrokeWidth; + let tickTextAlign = textAlign; if (isHorizontal) { x = pixel; + + if (textAlign === 'inner') { + if (i === ilen - 1) { + tickTextAlign = !this.options.reverse ? 'right' : 'left'; + } else if (i === 0) { + tickTextAlign = !this.options.reverse ? 'left' : 'right'; + } else { + tickTextAlign = 'center'; + } + } + if (position === 'top') { if (crossAlign === 'near' || rotation !== 0) { textOffset = -lineCount * lineHeight + lineHeight / 2; @@ -1285,7 +1297,7 @@ export default class Scale extends Element { strokeColor, strokeWidth, textOffset, - textAlign, + textAlign: tickTextAlign, textBaseline, translation: [x, y], backdrop, @@ -1309,6 +1321,8 @@ export default class Scale extends Element { align = 'left'; } else if (ticks.align === 'end') { align = 'right'; + } else if (ticks.align === 'inner') { + align = 'inner'; } return align; diff --git a/test/fixtures/core.scale/label-align-inner-onlyX.js b/test/fixtures/core.scale/label-align-inner-onlyX.js new file mode 100644 index 00000000000..a64157c3134 --- /dev/null +++ b/test/fixtures/core.scale/label-align-inner-onlyX.js @@ -0,0 +1,30 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1_long', 'Label2_long', 'Label3_long'] + }, + options: { + scales: { + x: { + ticks: { + align: 'inner', + }, + }, + y: { + display: false + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-inner-onlyX.png b/test/fixtures/core.scale/label-align-inner-onlyX.png new file mode 100644 index 00000000000..7d5b039b502 Binary files /dev/null and b/test/fixtures/core.scale/label-align-inner-onlyX.png differ diff --git a/test/fixtures/core.scale/label-align-inner-reverse.js b/test/fixtures/core.scale/label-align-inner-reverse.js new file mode 100644 index 00000000000..d56f6a04fd1 --- /dev/null +++ b/test/fixtures/core.scale/label-align-inner-reverse.js @@ -0,0 +1,28 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'inner' + }, + reverse: true + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-inner-reverse.png b/test/fixtures/core.scale/label-align-inner-reverse.png new file mode 100644 index 00000000000..f1d8330bfed Binary files /dev/null and b/test/fixtures/core.scale/label-align-inner-reverse.png differ diff --git a/test/fixtures/core.scale/label-align-inner-rotate.js b/test/fixtures/core.scale/label-align-inner-rotate.js new file mode 100644 index 00000000000..7cee63a9556 --- /dev/null +++ b/test/fixtures/core.scale/label-align-inner-rotate.js @@ -0,0 +1,29 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'inner', + maxRotation: 45, + minRotation: 45 + }, + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-inner-rotate.png b/test/fixtures/core.scale/label-align-inner-rotate.png new file mode 100644 index 00000000000..d3aca46fa5c Binary files /dev/null and b/test/fixtures/core.scale/label-align-inner-rotate.png differ diff --git a/test/fixtures/core.scale/label-align-inner.js b/test/fixtures/core.scale/label-align-inner.js new file mode 100644 index 00000000000..ef62e9b66af --- /dev/null +++ b/test/fixtures/core.scale/label-align-inner.js @@ -0,0 +1,27 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + scales: { + x: { + ticks: { + align: 'inner', + }, + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-inner.png b/test/fixtures/core.scale/label-align-inner.png new file mode 100644 index 00000000000..f420956f6af Binary files /dev/null and b/test/fixtures/core.scale/label-align-inner.png differ diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index ccea4128037..a4f463c25b1 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -3020,7 +3020,7 @@ export interface CartesianScaleOptions extends CoreScaleOptions { * The label alignment * @default 'center' */ - align: Align; + align: Align | 'inner'; /** * If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to maxRotation before skipping any. Turn autoSkip off to show all labels no matter what. * @default true