Skip to content

Positive and negative values

Values below zero need one thing decided: where bars grow from. Pin base to 0 and everything else — bar direction, label placement, caps, stacking — follows the sign on its own.

ts
// Without stacks the axis base defaults to the domain minimum, so mixed-sign
// bars would all grow up from the most negative value — base: 0 makes them
// grow out of zero instead. The outside labels flip on their own: above
// positive bars, below negative ones.
import type { MochartInputConfig } from '@mochart/core';

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Monthly Net Cash Flow' },
  categoryAxis: { property: 'month', type: 'string', scale: 'ordinal' },
  // The extra min margin leaves room for the lowest bar's outside label.
  valueAxes: [{ id: 'VA0', title: 'Net cash flow ($k)', base: 0, minMarginFraction: 0.15 }],
  series: [
    {
      property: 'net',
      title: 'Net cash flow',
      renderer: 'bar',
      labelProperty: 'net',
      labelPosition: 'outside',
      labelFormat: ',.0f'
    }
  ]
};

export const data = [
  { month: 'Jan', net: 42 },
  { month: 'Feb', net: 28 },
  { month: 'Mar', net: -15 },
  { month: 'Apr', net: 33 },
  { month: 'May', net: -8 },
  { month: 'Jun', net: 51 },
  { month: 'Jul', net: 12 },
  { month: 'Aug', net: -24 }
];

How it works

  • Without stacks, base defaults to null — the axis domain minimum — so mixed-sign bars would all grow upward from the most negative value. base: 0 makes bars grow out of zero in both directions, and the divide is marked by the default base line (showBaseLine, styled with baseLineStyle).
  • The domain fits the data on both sides. When the values might sit all on one side, softMin / softMax 0 hold zero in view without ever clipping data — unlike min/max, which clamp hard.
  • Labels flip with the sign: an outsidelabelPosition sits above positive bars and below negative ones automatically. Each side can be tuned separately with the labelAboveBase* / labelBelowBase* family — position, offset, and min/max position bounds — whose auto defaults inherit the shared knobs.
  • The sign carries through the other bar features: caps point downward on negative bars, and colorScale.base.value: 0 colors gains and losses from two different ramps.

Stacking mixed signs

ts
// In a stack, each sign accumulates separately from the shared zero base:
// positive segments stack upward, negative segments stack downward. An axis
// with stacks defaults its base to 0 — no pinning needed.
import type { MochartInputConfig } from '@mochart/core';

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Quarterly Cash Flow' },
  categoryAxis: { property: 'quarter', type: 'string', scale: 'ordinal' },
  valueAxes: [{ id: 'VA0', title: 'Cash flow ($k)' }],
  seriesDefaults: { renderer: 'bar' },
  series: [
    { property: 'sales', title: 'Sales' },
    { property: 'services', title: 'Services' },
    { property: 'payroll', title: 'Payroll' },
    { property: 'infrastructure', title: 'Infrastructure' }
  ],
  seriesStacks: [{ id: 'cashflow' }]
};

export const data = [
  { quarter: 'Q1', sales: 68, services: 22, payroll: -48, infrastructure: -18 },
  { quarter: 'Q2', sales: 75, services: 26, payroll: -50, infrastructure: -21 },
  { quarter: 'Q3', sales: 71, services: 31, payroll: -52, infrastructure: -19 },
  { quarter: 'Q4', sales: 83, services: 34, payroll: -55, infrastructure: -23 }
];
  • Each sign accumulates separately from the shared base: a category's positive segments stack upward while its negative segments stack downward, so inflows and outflows read as two piles growing out of zero. An axis with stacks defaults its base to 0 — no pinning needed.
  • Filtering a series in the legend re-stacks its own side and leaves the other untouched.
  • The stack's outer caps apply per sign — the topmost positive segment and the bottommost negative one each get one.
  • To show the net total across both piles, overlay a line series opted out of the stack with stack: null (see stacked bars); for a running total that crosses zero step by step, that's the waterfall.

Released under the BSD-3-Clause License.