[Bug]:
Author: codeCraft-RitikCreated Sep 10, 2026Updated Sep 10, 2026
Labelsbug
Would you like to work on a fix?
- Check this if you would like to implement a PR, we are more than happy to help you go through the process.
Current and expected behavior
Description
In Axis.createGridAndLabels, the available space for an axis label (labelLength) is calculated by measuring the distance to the next projected tick. However, when the next tick coordinate is 0, a truthiness check (if (projectedValues[index + 1])) evaluates to false, causing Chartist to incorrectly assume that no subsequent tick exists.
Location in Codebase
src/axes/Axis.ts(lines 86–98)
Root Cause Analysis
In src/axes/Axis.ts:
let labelLength;
if (projectedValues[index + 1]) {
// If we still have one label ahead, we can calculate the distance to the next tick / label
labelLength = projectedValues[index + 1] - projectedValue;
} else {
// If we don't have a label ahead...
labelLength = Math.max(
this.axisLength - projectedValue,
this.axisLength / this.ticks.length
);
}When an axis contains ticks where one tick projects to coordinate 0 (very common on bipolar scales or auto-scaled charts spanning negative to positive ranges, e.g. [-50, 0, 50]):
- At
index = 0(tick-50), the next tick's projected coordinate isprojectedValues[1] = 0. - In JavaScript,
if (0)evaluates tofalse. - Instead of calculating the distance to the next tick (
0 - (-50) = 50px), the code executes theelsebranch, settinglabelLengthtoMath.max(axisLength - (-50), ...)(e.g.450px). - This passes an excessively large dimension to
createLabel(), setting an oversizedwidth/heighton the label<span>and<foreignObject>, which leads to text overlapping and distorted layout bounding boxes.
Expected vs Actual Behavior
- Expected: When
projectedValues = [-50, 0, 50]atindex = 0,labelLengthshould be0 - (-50) = 50. - Actual:
if (0)evaluates tofalse, causing the fallback calculation to assignlabelLength = Math.max(axisLength - (-50), ...).
Suggested Fix
Check for index / undefined existence instead of numeric truthiness:
if (projectedValues[index + 1] !== undefined) {
labelLength = projectedValues[index + 1] - projectedValue;
} else {
...
}Reproduction
### Chartist version
1.5.0 (latest / main)
### Possible solution
// ============================================================================
// 1. SOURCE CODE FIX: src/axes/Axis.ts
// ============================================================================
// ❌ BEFORE (BUG):
// let labelLength;
// if (projectedValues[index + 1]) {
// labelLength = projectedValues[index + 1] - projectedValue;
// } else { ... }
// ✅ AFTER (FIX):
let labelLength;
if (projectedValues[index + 1] !== undefined) {
labelLength = projectedValues[index + 1] - projectedValue;
} else {
labelLength = Math.max(
this.axisLength - projectedValue,
this.axisLength / this.ticks.length
);
}
// ============================================================================
// 2. UNIT TEST TO ADD: src/axes/Axis.spec.ts
// ============================================================================
// Add this test to `describe('Axis', () => { ... })` in `src/axes/Axis.spec.ts`:
it('should correctly calculate labelLength when next projected value is 0', () => {
const chartRect = {
x1: 50,
x2: 450,
y1: 300,
y2: 50,
padding: { top: 0, right: 0, bottom: 0, left: 0 },
width() { return this.x2 - this.x1; },
height() { return this.y1 - this.y2; }
};
class TestAxis extends Axis {
projectValue(val: number) {
return val; // Returns raw projected values: -50, 0, 50
}
}
const axis = new TestAxis(axisUnits.x, chartRect as any, [-50, 0, 50]);
const gridGroup = new Svg('g');
const labelGroup = new Svg('g');
const eventEmitter = new EventEmitter();
const emittedLabels: any[] = [];
eventEmitter.on('draw', data => {
if (data.type === 'label') emittedLabels.push(data);
});
axis.createGridAndLabels(gridGroup, labelGroup, {
axisX: {
showGrid: true,
showLabel: true,
labelInterpolationFnc: (v: any) => v,
offset: 30,
position: 'end',
labelOffset: { x: 0, y: 0 }
},
axisY: {} as any,
classNames: { grid: 'ct-grid', label: 'ct-label', horizontal: 'ct-horizontal', end: 'ct-end' }
} as any, eventEmitter);
// For index 0 (value -50) where next tick is 0:
// Distance should be 0 - (-50) = 50px, not the entire axis length fallback
const firstLabelSpan = labelGroup.getNode().querySelectorAll('span')[0];
expect(firstLabelSpan.style.width).toBe('50px');
});Source: chartist-js/chartist