title: Cartesian Axes
---
+import { useEffect } from 'react';
+
Axes that follow a cartesian grid are known as 'Cartesian Axes'. Cartesian axes are used for line, bar, and bubble charts. Four cartesian axes are included in Chart.js by default.
* [linear](./linear.md#linear-cartesian-axis)
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
+| `alignment` | `string` | | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, or `'end'`.
+| `crossAlignment` | `string` | | `'near'` | The tick alignment perpendicular to the axis. Can be `'near'`, `'center'`, or `'far'`. See [Tick Alignment](#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.
| `autoSkipPadding` | `number` | `0` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled.
| `mirror` | `boolean` | `false` | Flips tick labels around axis, displaying the labels inside the chart instead of outside. *Note: Only applicable to vertical scales.*
| `padding` | `number` | `0` | Padding between the tick label and the axis. When set on a vertical axis, this applies in the horizontal (X) direction. When set on a horizontal axis, this applies in the vertical (Y) direction.
+### Tick Alignment
+
+The alignment of ticks is primarily controlled using two settings on the tick configuration object: `alignment` and `crossAlignment`. The `alignment` setting configures how labels align with the tick mark along the axis direction (i.e. horizontal for a horizontal axis and vertical for a vertical axis). The `crossAlignment` setting configures how labels align with the tick mark in the perpendicular direction (i.e. vertical for a horizontal axis and horizontal for a vertical axis). In the example below, the `crossAlignment` setting is used to left align the labels on the Y axis.
+
+```jsx live
+function exampleAlignment() {
+ useEffect(() => {
+ const cfg = {
+ type: 'bar',
+ data: {
+ labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
+ datasets: [{
+ axis: 'y',
+ label: 'My First Dataset',
+ data: [65, 59, 80, 81, 56, 55, 40],
+ fill: false,
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)',
+ 'rgba(255, 159, 64, 0.2)',
+ 'rgba(255, 205, 86, 0.2)',
+ 'rgba(75, 192, 192, 0.2)',
+ 'rgba(54, 162, 235, 0.2)',
+ 'rgba(153, 102, 255, 0.2)',
+ 'rgba(201, 203, 207, 0.2)'
+ ],
+ borderColor: [
+ 'rgb(255, 99, 132)',
+ 'rgb(255, 159, 64)',
+ 'rgb(255, 205, 86)',
+ 'rgb(75, 192, 192)',
+ 'rgb(54, 162, 235)',
+ 'rgb(153, 102, 255)',
+ 'rgb(201, 203, 207)'
+ ],
+ borderWidth: 1
+ }]
+ },
+ options: {
+ indexAxis: 'y',
+ scales: {
+ x: {
+ beginAtZero: true
+ },
+ y: {
+ ticks: {
+ crossAlignment: 'far',
+ }
+ }
+ }
+ }
+ };
+ new Chart(document.getElementById('chartjs-1').getContext('2d'), cfg);
+ });
+ return <div className="chartjs-wrapper"><canvas id="chartjs-1" className="chartjs"></canvas></div>;
+}
+```
+
+**Note:** the `crossAlignment` setting is not used the the tick rotation is not `0`, the axis position is `'center'` or the position is with respect to a data value.
+
### Axis ID
The properties `dataset.xAxisID` or `dataset.yAxisID` have to match to `scales` property. This is especially needed if multi-axes charts are used.
minor: {},
major: {},
alignment: 'center',
+ crossAlignment: 'near',
}
});
const axis = me.axis;
const options = me.options;
const {position, ticks: optionTicks} = options;
- const isMirrored = optionTicks.mirror;
const isHorizontal = me.isHorizontal();
const ticks = me.ticks;
- const tickPadding = optionTicks.padding;
+ const {alignment, crossAlignment, padding} = optionTicks;
const tl = getTickMarkLength(options.gridLines);
+ const tickAndPadding = tl + padding;
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;
+ y = me.bottom - tickAndPadding;
textAlign = me._getXAxisLabelAlignment();
} else if (position === 'bottom') {
- y = me.top + tl + tickPadding;
+ y = me.top + tickAndPadding;
textAlign = me._getXAxisLabelAlignment();
} else if (position === 'left') {
- x = me.right - (isMirrored ? 0 : tl) - tickPadding;
- textAlign = isMirrored ? 'left' : 'right';
+ const ret = this._getYAxisLabelAlignment(tl);
+ textAlign = ret.textAlign;
+ x = ret.x;
} else if (position === 'right') {
- x = me.left + (isMirrored ? 0 : tl) + tickPadding;
- textAlign = isMirrored ? 'right' : 'left';
+ const ret = this._getYAxisLabelAlignment(tl);
+ textAlign = ret.textAlign;
+ x = ret.x;
} else if (axis === 'x') {
if (position === 'center') {
- y = ((chartArea.top + chartArea.bottom) / 2) + tl + tickPadding;
+ y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
- y = me.chart.scales[positionAxisID].getPixelForValue(value) + tl + tickPadding;
+ y = me.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
}
textAlign = me._getXAxisLabelAlignment();
} else if (axis === 'y') {
if (position === 'center') {
- x = ((chartArea.left + chartArea.right) / 2) - tl - tickPadding;
+ x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
x = me.chart.scales[positionAxisID].getPixelForValue(value);
}
- textAlign = 'right';
+ textAlign = this._getYAxisLabelAlignment(tl).textAlign;
}
if (axis === 'y') {
- if (optionTicks.alignment === 'start') {
+ if (alignment === 'start') {
textBaseline = 'top';
- } else if (optionTicks.alignment === 'end') {
+ } else if (alignment === 'end') {
textBaseline = 'bottom';
}
}
+ const labelSizes = me._getLabelSizes();
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
tick = ticks[i];
label = tick.label;
font = me._resolveTickFontOptions(i);
lineHeight = font.lineHeight;
lineCount = isArray(label) ? label.length : 1;
+ const halfCount = lineCount / 2;
if (isHorizontal) {
x = pixel;
if (position === 'top') {
- textOffset = (Math.sin(rotation) * (lineCount / 2) + 0.5) * lineHeight;
- textOffset -= (rotation === 0 ? (lineCount - 0.5) : Math.cos(rotation) * (lineCount / 2)) * lineHeight;
- } else {
- textOffset = Math.sin(rotation) * (lineCount / 2) * lineHeight;
- textOffset += (rotation === 0 ? 0.5 : Math.cos(rotation) * (lineCount / 2)) * lineHeight;
+ if (crossAlignment === 'near' || rotation !== 0) {
+ textOffset = (Math.sin(rotation) * halfCount + 0.5) * lineHeight;
+ textOffset -= (rotation === 0 ? (lineCount - 0.5) : Math.cos(rotation) * halfCount) * lineHeight;
+ } else if (crossAlignment === 'center') {
+ textOffset = -1 * (labelSizes.highest.height / 2);
+ textOffset -= halfCount * lineHeight;
+ } else {
+ textOffset = (-1 * labelSizes.highest.height) + (0.5 * lineHeight);
+ }
+ } else if (position === 'bottom') {
+ if (crossAlignment === 'near' || rotation !== 0) {
+ textOffset = Math.sin(rotation) * halfCount * lineHeight;
+ textOffset += (rotation === 0 ? 0.5 : Math.cos(rotation) * halfCount) * lineHeight;
+ } else if (crossAlignment === 'center') {
+ textOffset = labelSizes.highest.height / 2;
+ textOffset -= halfCount * lineHeight;
+ } else {
+ textOffset = labelSizes.highest.height - ((lineCount - 0.5) * lineHeight);
+ }
}
} else {
y = pixel;
return align;
}
+ _getYAxisLabelAlignment(tl) {
+ const me = this;
+ const {position, ticks} = me.options;
+ const {crossAlignment, mirror, padding} = ticks;
+ const labelSizes = me._getLabelSizes();
+ const tickAndPadding = tl + padding;
+ const widest = labelSizes.widest.width;
+
+ let textAlign;
+ let x;
+
+ if (position === 'left') {
+ if (mirror) {
+ textAlign = 'left';
+ x = me.right - padding;
+ } else {
+ x = me.right - tickAndPadding;
+
+ if (crossAlignment === 'near') {
+ textAlign = 'right';
+ } else if (crossAlignment === 'center') {
+ textAlign = 'center';
+ x -= (widest / 2);
+ } else {
+ textAlign = 'left';
+ x -= widest;
+ }
+ }
+ } else if (position === 'right') {
+ if (mirror) {
+ textAlign = 'right';
+ x = me.left + padding;
+ } else {
+ x = me.left + tickAndPadding;
+
+ if (crossAlignment === 'near') {
+ textAlign = 'left';
+ } else if (crossAlignment === 'center') {
+ textAlign = 'center';
+ x += widest / 2;
+ } else {
+ textAlign = 'right';
+ x += widest;
+ }
+ }
+ } else {
+ textAlign = 'right';
+ }
+
+ return {textAlign, x};
+ }
+
/**
* @protected
*/