mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Cartesian axis text alignment (#7846)
* Generate textBaseline per tick label * Enable configuration of tick alignment * Add image based tests of text alignment options
This commit is contained in:
parent
ef6a0e176c
commit
dc4eac6323
@ -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)
|
||||
|
||||
@ -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'
|
||||
|
||||
163
samples/scales/label-text-alignment.html
Normal file
163
samples/scales/label-text-alignment.html
Normal file
@ -0,0 +1,163 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Label Text Alignment</title>
|
||||
<script src="../../dist/chart.min.js"></script>
|
||||
<script src="../utils.js"></script>
|
||||
<style>
|
||||
canvas {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
.chart-container {
|
||||
width: 500px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-start-start"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-start-center"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-start-end"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-center-start"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-center-center"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-center-end"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-end-start"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-end-center"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart-end-end"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var color = Chart.helpers.color;
|
||||
function createConfig(xAlign, yAlign, colorName) {
|
||||
return {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||
datasets: [{
|
||||
label: 'My First dataset',
|
||||
data: [
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor(),
|
||||
randomScalingFactor()
|
||||
],
|
||||
backgroundColor: color(window.chartColors[colorName]).alpha(0.5).rgbString(),
|
||||
borderColor: window.chartColors[colorName],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
ticks: {
|
||||
alignment: xAlign,
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
ticks: {
|
||||
alignment: yAlign
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'X Tick Alignment: ' + xAlign + ', Y Tick Alignment ' + yAlign
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
[{
|
||||
id: 'chart-start-start',
|
||||
xAlign: 'start',
|
||||
yAlign: 'start',
|
||||
color: 'red'
|
||||
}, {
|
||||
id: 'chart-start-center',
|
||||
xAlign: 'start',
|
||||
yAlign: 'center',
|
||||
color: 'orange'
|
||||
}, {
|
||||
id: 'chart-start-end',
|
||||
xAlign: 'start',
|
||||
yAlign: 'end',
|
||||
color: 'yellow'
|
||||
}, {
|
||||
id: 'chart-center-start',
|
||||
xAlign: 'center',
|
||||
yAlign: 'start',
|
||||
color: 'green'
|
||||
}, {
|
||||
id: 'chart-center-center',
|
||||
xAlign: 'center',
|
||||
yAlign: 'center',
|
||||
color: 'blue'
|
||||
}, {
|
||||
id: 'chart-center-end',
|
||||
xAlign: 'center',
|
||||
yAlign: 'end',
|
||||
color: 'purple'
|
||||
}, {
|
||||
id: 'chart-end-start',
|
||||
xAlign: 'end',
|
||||
yAlign: 'start',
|
||||
color: 'grey'
|
||||
}, {
|
||||
id: 'chart-end-center',
|
||||
xAlign: 'end',
|
||||
yAlign: 'center',
|
||||
color: 'red'
|
||||
}, {
|
||||
id: 'chart-end-end',
|
||||
xAlign: 'end',
|
||||
yAlign: 'end',
|
||||
color: 'orange'
|
||||
}].forEach(function(details) {
|
||||
var ctx = document.getElementById(details.id).getContext('2d');
|
||||
var config = createConfig(details.xAlign, details.yAlign, details.color);
|
||||
new Chart(ctx, config);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -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;
|
||||
|
||||
34
test/fixtures/core.scale/label-align-center.js
vendored
Normal file
34
test/fixtures/core.scale/label-align-center.js
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/core.scale/label-align-center.png
vendored
Normal file
BIN
test/fixtures/core.scale/label-align-center.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
34
test/fixtures/core.scale/label-align-end.js
vendored
Normal file
34
test/fixtures/core.scale/label-align-end.js
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/core.scale/label-align-end.png
vendored
Normal file
BIN
test/fixtures/core.scale/label-align-end.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
34
test/fixtures/core.scale/label-align-start.js
vendored
Normal file
34
test/fixtures/core.scale/label-align-start.js
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/core.scale/label-align-start.png
vendored
Normal file
BIN
test/fixtures/core.scale/label-align-start.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
5
types/scales/index.d.ts
vendored
5
types/scales/index.d.ts
vendored
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user