From: Evert Timberg Date: Sat, 10 Oct 2020 15:38:55 +0000 (-0400) Subject: Cartesian axis text alignment (#7846) X-Git-Tag: v3.0.0-beta.4~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dc4eac63233a7ba15c20fe03f8f3ac34584d134a;p=thirdparty%2FChart.js.git Cartesian axis text alignment (#7846) * Generate textBaseline per tick label * Enable configuration of tick alignment * Add image based tests of text alignment options --- diff --git a/docs/docs/axes/styling.md b/docs/docs/axes/styling.md index a9c8f1a0b..eed36ab4d 100644 --- a/docs/docs/axes/styling.md +++ b/docs/docs/axes/styling.md @@ -42,6 +42,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | Name | Type | Scriptable | Default | Description | ---- | ---- | :-------------------------------: | ------- | ----------- +| `alignment` | `string` | | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, or `'end'`. | `callback` | `function` | | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). | `display` | `boolean` | | `true` | If true, show tick labels. | `font` | `Font` | Yes | `defaults.font` | See [Fonts](../general/fonts.md) diff --git a/samples/samples.js b/samples/samples.js index 67d1f4486..57cac6b3a 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -145,6 +145,9 @@ }, { title: 'Filtering Labels', path: 'scales/filtering-labels.html' + }, { + title: 'Label Text Alignment', + path: 'scales/label-text-alignment.html' }, { title: 'Non numeric Y Axis', path: 'scales/non-numeric-y.html' diff --git a/samples/scales/label-text-alignment.html b/samples/scales/label-text-alignment.html new file mode 100644 index 000000000..4e542692f --- /dev/null +++ b/samples/scales/label-text-alignment.html @@ -0,0 +1,163 @@ + + + + + Label Text Alignment + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 2a39bd937..69a69be76 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -61,7 +61,8 @@ defaults.set('scale', { // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. callback: Ticks.formatters.values, minor: {}, - major: {} + major: {}, + alignment: 'center', } }); @@ -761,6 +762,12 @@ export default class Scale extends Element { paddingRight = labelsBelowTicks ? sinRotation * (lastLabelSize.height - lastLabelSize.offset) : cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; + } else if (tickOpts.alignment === 'start') { + paddingLeft = 0; + paddingRight = lastLabelSize.width; + } else if (tickOpts.alignment === 'end') { + paddingLeft = firstLabelSize.width; + paddingRight = 0; } else { paddingLeft = firstLabelSize.width / 2; paddingRight = lastLabelSize.width / 2; @@ -780,8 +787,19 @@ export default class Scale extends Element { minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - me.paddingTop = lastLabelSize.height / 2; - me.paddingBottom = firstLabelSize.height / 2; + let paddingTop = lastLabelSize.height / 2; + let paddingBottom = firstLabelSize.height / 2; + + if (tickOpts.alignment === 'start') { + paddingTop = 0; + paddingBottom = firstLabelSize.height; + } else if (tickOpts.alignment === 'end') { + paddingTop = lastLabelSize.height; + paddingBottom = 0; + } + + me.paddingTop = paddingTop; + me.paddingBottom = paddingBottom; } } @@ -1240,13 +1258,14 @@ export default class Scale extends Element { const rotation = -toRadians(me.labelRotation); const items = []; let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + let textBaseline = 'middle'; if (position === 'top') { y = me.bottom - tl - tickPadding; - textAlign = !rotation ? 'center' : 'left'; + textAlign = me._getXAxisLabelAlignment(); } else if (position === 'bottom') { y = me.top + tl + tickPadding; - textAlign = !rotation ? 'center' : 'right'; + textAlign = me._getXAxisLabelAlignment(); } else if (position === 'left') { x = me.right - (isMirrored ? 0 : tl) - tickPadding; textAlign = isMirrored ? 'left' : 'right'; @@ -1261,7 +1280,7 @@ export default class Scale extends Element { const value = position[positionAxisID]; y = me.chart.scales[positionAxisID].getPixelForValue(value) + tl + tickPadding; } - textAlign = !rotation ? 'center' : 'right'; + textAlign = me._getXAxisLabelAlignment(); } else if (axis === 'y') { if (position === 'center') { x = ((chartArea.left + chartArea.right) / 2) - tl - tickPadding; @@ -1273,6 +1292,14 @@ export default class Scale extends Element { textAlign = 'right'; } + if (axis === 'y') { + if (optionTicks.alignment === 'start') { + textBaseline = 'top'; + } else if (optionTicks.alignment === 'end') { + textBaseline = 'bottom'; + } + } + for (i = 0, ilen = ticks.length; i < ilen; ++i) { tick = ticks[i]; label = tick.label; @@ -1303,13 +1330,34 @@ export default class Scale extends Element { label, font, textOffset, - textAlign + textAlign, + textBaseline, }); } return items; } + _getXAxisLabelAlignment() { + const me = this; + const {position, ticks} = me.options; + const rotation = -toRadians(me.labelRotation); + + if (rotation) { + return position === 'top' ? 'left' : 'right'; + } + + let align = 'center'; + + if (ticks.alignment === 'start') { + align = 'left'; + } else if (ticks.alignment === 'end') { + align = 'right'; + } + + return align; + } + /** * @protected */ @@ -1409,8 +1457,8 @@ export default class Scale extends Element { ctx.rotate(item.rotation); ctx.font = tickFont.string; ctx.fillStyle = tickFont.color; - ctx.textBaseline = 'middle'; ctx.textAlign = item.textAlign; + ctx.textBaseline = item.textBaseline; if (useStroke) { ctx.strokeStyle = tickFont.strokeStyle; diff --git a/test/fixtures/core.scale/label-align-center.js b/test/fixtures/core.scale/label-align-center.js new file mode 100644 index 000000000..1e9907bf9 --- /dev/null +++ b/test/fixtures/core.scale/label-align-center.js @@ -0,0 +1,34 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + legend: false, + title: false, + scales: { + x: { + ticks: { + alignment: 'center', + }, + }, + y: { + ticks: { + alignment: 'center', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-center.png b/test/fixtures/core.scale/label-align-center.png new file mode 100644 index 000000000..576f0254d Binary files /dev/null and b/test/fixtures/core.scale/label-align-center.png differ diff --git a/test/fixtures/core.scale/label-align-end.js b/test/fixtures/core.scale/label-align-end.js new file mode 100644 index 000000000..19a0e8ad3 --- /dev/null +++ b/test/fixtures/core.scale/label-align-end.js @@ -0,0 +1,34 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + legend: false, + title: false, + scales: { + x: { + ticks: { + alignment: 'end', + }, + }, + y: { + ticks: { + alignment: 'end', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-end.png b/test/fixtures/core.scale/label-align-end.png new file mode 100644 index 000000000..13d72d25d Binary files /dev/null and b/test/fixtures/core.scale/label-align-end.png differ diff --git a/test/fixtures/core.scale/label-align-start.js b/test/fixtures/core.scale/label-align-start.js new file mode 100644 index 000000000..b5284cd96 --- /dev/null +++ b/test/fixtures/core.scale/label-align-start.js @@ -0,0 +1,34 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [1, 2, 3], + }], + labels: ['Label1', 'Label2', 'Label3'] + }, + options: { + legend: false, + title: false, + scales: { + x: { + ticks: { + alignment: 'start', + }, + }, + y: { + ticks: { + alignment: 'start', + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.scale/label-align-start.png b/test/fixtures/core.scale/label-align-start.png new file mode 100644 index 000000000..ad7c280f4 Binary files /dev/null and b/test/fixtures/core.scale/label-align-start.png differ diff --git a/types/scales/index.d.ts b/types/scales/index.d.ts index 682116743..fe19273c6 100644 --- a/types/scales/index.d.ts +++ b/types/scales/index.d.ts @@ -128,6 +128,11 @@ export interface ICartesianScaleOptions extends ICoreScaleOptions { * @default ticks.length */ sampleSize: number; + /** + * The label alignment + * @default 'center' + */ + alignment: 'start' | 'center' | 'end'; /** * 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