Skip to content

Arrows

Difference, CAGR, and level arrows live under annotations as id-keyed maps. Each entry is one arrow.

KindchartData pathWhat it shows
Differenceannotations.differenceArrows.<id>Absolute and/or % delta between two columns
CAGRannotations.cagrs.<id>Compound annual growth between two columns
Levelannotations.levelArrows.<id>Horizontal compare between two anchor points

CAGR and difference use simple column indices. Level arrows need fuller anchorPoints geometry (barTop / linePoint, columnIndex, seriesIndex), so they are the easiest of the three to mis-wire by hand.

Chart type support

KindStacked / clustered bar100% stackedWaterfallCombo (bars)Line / areaPie / scatter / mekko
Differenceyesyes*yesyesnono
CAGRyesnoyesyesnono
Levelyesyesyesyesyesno

*Engine can draw difference on 100% stacked; the editor add menu may not offer it on every type. Combo arrows attach to bar series only.

Shared styling

These fields are common across the three arrow maps:

FieldTypeTypical defaultNotes
lineColorstring'#000000'Stroke color
lineWidthnumber1.5Stroke width (px)
lineStylestring'solid'solid · dashed · densely-dashed · dotted · dash-dot
labelShapestring'pill'none · rounded · pill · circle
fontSizenumber810Label size (pt)
chartTypestringcurrent typeSnapshotted when the arrow was created

Category anchors (start / end / columnIndex) are 0-based column indices in seriesData (after the header row / label column).

Labels

displayMode (and default labelParts) drive the computed absolute / % text. Optional custom copy uses label on level arrows, and text or label on difference / CAGR arrows.

If you set custom text and leave a computed mode active, both can render (for example +36 next to another +36, or Expansion plus a computed +15%). For custom text only:

js
label: 'Expansion',
labelParts: ['label'],

To show custom text plus a computed part: labelParts: ['label', 'percentage'] (or 'absolute').

Layout offsets

Fields like verticalLinePositionIndex, horizontalYOffset, and CAGR’s startYOffset / endYOffset are usually set by dragging in edit mode. When hand-authoring, start with null / omit offsets where possible, or copy them from an exported config.

Difference arrows (annotations.differenceArrows)

U-shaped connector between two columns. Label is usually a computed delta (displayMode / labelParts).

js
annotations: {
  differenceArrows: {
    q1VsQ2: {
      start: 0,
      end: 1,
      startSeries: null, // null = bar tops / stack totals
      endSeries: null,
      displayMode: 'percentage',
      chartType: 'stackedBar',
      lineColor: '#111827',
      lineWidth: 1.5,
      lineStyle: 'solid',
      labelShape: 'pill',
    },
  },
}
FieldMeaning
start / endColumn indices
startSeries / endSeriesSeries index, or null for tops / totals
displayModepercentage · absolute · absoluteWithPercentage · percentageWithAbsolute
labelPartsComposite label parts (e.g. ['percentage'], ['label','percentage'])
label / textOptional custom text
numberFormatAbsolute / numberFormatPercentagePer-arrow number specs — see Number formats
horizontalYOffsetDrag offset for the horizontal segment

CAGR arrows (annotations.cagrs)

Diagonal growth-rate arrow. Duration comes from the category span (|end - start|), or the series span when comparing within a column.

js
annotations: {
  cagrs: {
    fySpan: {
      start: 0,
      end: 5,
      startSeries: null,
      endSeries: null,
      chartType: 'stackedBar',
      lineColor: '#000000',
      lineWidth: 1.5,
      lineStyle: 'solid',
      startYOffset: -8,
      endYOffset: -8,
    },
  },
}
FieldMeaning
start / endColumn indices
startSeries / endSeriesSeries index, or null for tops / totals
textOptional custom label override
startYOffset / endYOffsetPer-endpoint vertical drag offsets

On waterfall, CAGR uses cumulative totals (not step deltas).

Level arrows (annotations.levelArrows)

Horizontal comparison between two explicit anchors (bar tops, segments, or line points).

js
annotations: {
  levelArrows: {
    seriesCompare: {
      anchorPoints: [
        { columnIndex: 4, seriesIndex: 2, type: 'barTop' },
        { columnIndex: 5, seriesIndex: 2, type: 'barTop' },
      ],
      verticalLinePositionIndex: null, // or a snap index from the editor
      displayMode: 'percentage',
      chartType: 'stackedBar',
      lineColor: '#000000',
      lineWidth: 1.5,
      lineStyle: 'solid',
      label: '',
      labelShape: 'pill',
      fontSize: 8,
    },
  },
}

Custom label only (no computed delta beside it):

js
label: '+36',
labelParts: ['label'],

Line-chart variant:

js
anchorPoints: [
  { columnIndex: 0, seriesIndex: 0, type: 'linePoint' },
  { columnIndex: 4, seriesIndex: 0, type: 'linePoint' },
]
FieldMeaning
anchorPointsTwo anchors: columnIndex, seriesIndex (or 'total' / null), optional type
verticalLinePositionIndexSnap index for the vertical rail / label (null = engine default)
displayMode / labelPartsWhat the label shows
labelOptional custom text

levelArrows is not seeded in defaults; the bag appears when the first level arrow is added.

Authoring tip

Add arrows in edit mode (or Hub MCP manage_annotations), then snapshot with getChartData() / exportConfig(). That picks up anchors and layout offsets without guessing snap indices.

See also Level lines.

Developer documentation for ChartBuddy Embed · Not the end-user Help Center · Help Center