#1469·chartist

[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

When initializing a BarChart with stackBars: true and stackMode: 'accumulate-relative', negative values in series data fail to stack below the zero line. Instead, they are incorrectly treated as positive values and placed into posStackedBarValues.

Location in Codebase

Root Cause Analysis

In BarChart.ts:

typescript
const valueX = safeHasProperty(value, 'x') && value.x;
const valueY = safeHasProperty(value, 'y') && value.y;

When standard 1D series data is provided (such as series: [[4, -4], [3, -5]]), the values in a vertical bar chart are normalized as { x: undefined, y: -4 }.

Because value does not have an owned property 'x', safeHasProperty(value, 'x') returns false. As a result:

typescript
valueX = false;

Later, the polarity of the bar is checked to select the stack container:

typescript
if (isAccumulateRelativeStackMode) {
  stackedBarValues =
    valueY >= 0 || valueX >= 0
      ? posStackedBarValues
      : negStackedBarValues;
}

In JavaScript, comparing a boolean to a number performs type coercion:

javascript
false >= 0; // evaluates to true (0 >= 0)

Because valueX >= 0 (false >= 0) is always true, the expression valueY >= 0 || valueX >= 0 evaluates to true for every negative value.

  • Negative bars are never placed into negStackedBarValues.
  • Negative bars in vertical charts are drawn on top of positive stacks in the positive coordinate space (above zero) rather than accumulating downward into negative space.
  • In horizontal bar charts (horizontalBars: true), valueY evaluates to false, causing the identical bug in reverse.

Steps to Reproduce

typescript
import { BarChart } from 'chartist';

new BarChart(
  '#chart',
  {
    labels: ['Category 1'],
    series: [
      [4],
      [-4]
    ]
  },
  {
    stackBars: true,
    stackMode: 'accumulate-relative'
  }
);

Expected vs Actual Behavior

  • Expected Behavior: The 4 bar starts at zeroPoint and extends into positive coordinate space. The -4 bar starts at zeroPoint and accumulates into negative coordinate space (below the zero line).
  • Actual Behavior: The -4 bar is placed in posStackedBarValues and is drawn backwards in positive space from y=80 down to y=100, cancelling out the positive bar.

Suggested Fix

Extract the dimension value based on chart orientation (horizontalBars) and avoid boolean coercion:

typescript
const valueX = safeHasProperty(value, 'x') ? value.x : undefined;
const valueY = safeHasProperty(value, 'y') ? value.y : undefined;

And in the stack selection logic:

typescript
if (isAccumulateRelativeStackMode) {
  const activeValue = options.horizontalBars ? valueX : valueY;
  stackedBarValues =
    activeValue !== undefined && activeValue >= 0
      ? posStackedBarValues
      : negStackedBarValues;
}

Reproduction

This can be reproduced directly using the repository's own example at sandboxes/bar/stacked-accumulate-relative/index.ts or with the minimal code below: ```typescript import 'chartist/dist/index.css'; import { BarChart } from 'chartist'; new BarChart( '#chart', { labels: ['Day 1'], series: [ [5], // Positive bar (+5) [-5] // Negative bar (-5) ] }, { stackBars: true, stackMode: 'accumulate-relative' } );

Chartist version

1.5.0 (latest / main)

Possible solution

// ============================================================================ // 1. SOURCE CODE FIX: src/charts/BarChart/BarChart.ts // ============================================================================

// ---------------------------------------------------------------------------- // [Line ~438]: Safely resolve valueX and valueY to number | undefined (not false) // ----------------------------------------------------------------------------

// ❌ BEFORE (BUG): // const valueX = safeHasProperty(value, 'x') && value.x; // const valueY = safeHasProperty(value, 'y') && value.y;

// ✅ AFTER (FIX): const valueX = safeHasProperty(value, 'x') ? value.x : undefined; const valueY = safeHasProperty(value, 'y') ? value.y : undefined;

// ---------------------------------------------------------------------------- // [Line ~513]: Select active dimension based on chart orientation before check // ----------------------------------------------------------------------------

// ❌ BEFORE (BUG): // if (isAccumulateRelativeStackMode) { // stackedBarValues = // valueY >= 0 || valueX >= 0 // ? posStackedBarValues // : negStackedBarValues; // }

// ✅ AFTER (FIX): if (isAccumulateRelativeStackMode) { const activeValue = options.horizontalBars ? valueX : valueY; stackedBarValues = activeValue !== undefined && activeValue >= 0 ? posStackedBarValues : negStackedBarValues; }

// ============================================================================ // 2. UNIT TESTS TO ADD: src/charts/BarChart/BarChart.spec.ts // ============================================================================ // Add this test block inside the describe('BarChart', () => { ... }) section:

describe('accumulate-relative stackMode', () => { it('should accumulate positive and negative values separately in vertical bar charts', async () => { data = { labels: ['Day 1'], series: [ [5], // Series 0 (Positive value) [-5] // Series 1 (Negative value) ] }; options = { stackBars: true, stackMode: 'accumulate-relative' }; await createChart();

const bars = fixture.wrapper.querySelectorAll('line.ct-bar');
expect(bars.length).toBe(2);

// In SVG coordinates, y values increase downwards:
// - Positive bar ends above zero (smaller y value).
// - Negative bar ends below zero (larger y value).
const posBarY2 = Number(bars[0].getAttribute('y2'));
const negBarY2 = Number(bars[1].getAttribute('y2'));

expect(posBarY2).toBeLessThan(negBarY2);

});

it('should accumulate positive and negative values separately in horizontal bar charts', async () => { data = { labels: ['Category 1'], series: [ [5], // Series 0 (Positive value) [-5] // Series 1 (Negative value) ] }; options = { stackBars: true, stackMode: 'accumulate-relative', horizontalBars: true }; await createChart();

const bars = fixture.wrapper.querySelectorAll('line.ct-bar');
expect(bars.length).toBe(2);

// In horizontal SVG coordinates, x values increase rightwards:
// - Positive bar extends to the right of zero (larger x value).
// - Negative bar extends to the left of zero (smaller x value).
const posBarX2 = Number(bars[0].getAttribute('x2'));
const negBarX2 = Number(bars[1].getAttribute('x2'));

expect(posBarX2).toBeGreaterThan(negBarX2);

}); });